跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言

FPGA 实现 OV5640 摄像头视频图像显示

基于 FPGA 实现 OV5640 摄像头视频图像显示的工程。主要功能包括通过 SCCB 协议初始化摄像头、DVP 接口采集数据、RAM 缓存图像以及 VGA 控制器输出至显示器。工程包含 SCCB 控制器、初始化表、DVP 控制、RAM 缓存及 VGA 控制等 Verilog 模块。实际测试中,VGA 以 640*480@60Hz 驱动,显示区域为 350*218,存在轻微画面撕裂和帧同步问题,后续可通过优化缓冲机制改进。

星落发布于 2026/4/5更新于 2026/9/474 浏览
FPGA 实现 OV5640 摄像头视频图像显示

一、工程介绍

OV5640 摄像头通过 DVP 接口输出视频图像数据,并通过 VGA 接口输出给显示器。FPGA 需要完成的功能包括:OV5640 初始化、DVP 接口数据采集、图像数据缓存、VGA 数据输出。模块设计也相应按照这四个部分进行划分。

文章配图

二、Verilog 实现

(1)OV5640 初始化

(1.1)SCCB 控制器

ov5640 摄像头初始化需要向其内部配置寄存器写入数据进行配置,实现对图像数据格式、图像大小、图像反转镜像、曝光、补偿等设置。对 ov5640 寄存器的读写操作需要遵循 SCCB 通信协议,不过 SCCB 与 I2C 协议很相似,SCCB 可以兼容 I2C,只是时序细节有区别,不过我工程中还是单独设计了一个 SCCB 控制器。

`timescale 1ns / 1ps // 适用于 ov5640 SCCB 通信 寄存器(字节)地址 16 位,数据 8 位
module SCCB_ctrl(
    input wire clk,           //系统时钟 100MHz
    input wire rst_n,         //复位
    inout wire sda,           //双向数据线(inout)
    output wire scl,          //输出时钟线
    input wire rw_ctrl,       //读写使能信号(0 写 1 读)
    input wire work_start,    //SCCB 启动信号
    input wire [6:0] slave_addr,//7bit 从设备地址
    input wire [15:0] byte_addr,//16bit 字地址
    input wire [7:0] w_data,  //8bit 待写数据
    output reg [7:0] r_data,  //8bit 读取数据
    output reg work_done      //SCCB 读写完成信号
);
    //sda 传输方向控制
    reg sda_oe;               //sda 输出使能,为 1 表示 sda 作输出
    reg sda_out;              //sda 输出信号线
    wire sda_in;              //sda 输入寄存器
    assign sda_in = sda;
    assign sda = sda_oe ? (sda_out ? 1'bz : 1'b0) : 1'bz;
    
    //状态机参数
    reg [4:0] state;          //当前状态
    localparam IDLE = 5'd0,   //空闲
               START = 5'd1,  //起始位
               W_SLAVE_ADDR = 5'd2, //写 7 位从设备地址 + 写命令 0
               ACK1 = 5'd3,   //应答 1
               W_H_BYTE_ADDR = 5'd4, //写高 8 位字地址
               ACK2 = 5'd5,   //应答 2
               W_L_BYTE_ADDR = 5'd6, //写低 8 位字地址
               ACK3 = 5'd7,   //应答 3
               STOP = 5'd8,   //停止位
               W_DATA = 5'd9, //写 8 位数据
               W_ACK = 5'd10, //写应答
               STOP2 = 5'd11, //中间停止位
               START2 = 5'd12,//中间起始位
               R_SLAVE_ADDR = 5'd13,//写 7 位从设备地址 + 读命令 1
               R_ACK = 5'd14, //读应答
               R_DATA = 5'd15,//读 8 位数据位
               N_ACK = 5'd16;//无应答
    
    //计数器及参数
    reg clk_div;
    reg [7:0] cnt_clk;        //分频计数
    reg [3:0] cnt_bit;        //位计数器
    localparam cnt_max_400khz = 8'd125; //400khz 分频翻转计算值
    wire scl_half_1;
    wire scl_half_0;
    wire scl_ack_jump;
    assign scl_half_1 = (cnt_clk == cnt_max_400khz >> 1 && clk_div==1'b1);
    assign scl_half_0 = (cnt_clk == cnt_max_400khz >> 1 && clk_div==1'b0);
    assign scl_ack_jump=((cnt_clk ==(cnt_max_400khz >> 1)-5) && clk_div==1'b0);
    
    //数据寄存器
    reg [7:0] w_data_buf;     //写入数据寄存器
    reg [7:0] r_data_buf;     //读出数据寄存器
    reg [7:0] w_slave_addr_buf;//从设备地址寄存器(地址存高 7 位,0 位为写命令 0)
    reg [7:0] r_slave_addr_buf;//从设备地址寄存器(地址存高 7 位,0 位为读命令 1)
    reg [7:0] H_byte_addr_buf;//字地址高 8 位寄存器
    reg [7:0] L_byte_addr_buf;//字地址低 8 位寄存器
    reg work_en;              //工作使能信号
    
    //*************************************** MAIN CODE ***************************************//
    //数据复位、开始工作时寄存数据(避免传输中途数据不稳定)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            w_slave_addr_buf <= 8'b0000_0000; //0 位为写命令 0
            r_slave_addr_buf <= 8'b0000_0001; //0 位为读命令 1
            H_byte_addr_buf <= 8'b0;
            L_byte_addr_buf <= 8'b0;
            w_data_buf <= 8'b0;
        end else if (work_start) begin
            w_slave_addr_buf [7:1] <= slave_addr; //地址存高 7 位
            r_slave_addr_buf [7:1] <= slave_addr; //地址存高 7 位
            w_data_buf <= w_data;
            H_byte_addr_buf <= byte_addr[15:8];
            L_byte_addr_buf <= byte_addr[7:0];
        end
    end
    
    //分频计数器 (400khz 时钟 scl)
    always @(posedge clk or negedge rst_n) begin
        if (!work_en || !rst_n) begin
            cnt_clk <= 8'd1;
            clk_div <= 1'b1;
        end else if (cnt_clk == cnt_max_400khz) begin
            cnt_clk <= 8'd1;
            clk_div <= ~clk_div;
        end else cnt_clk <= cnt_clk + 8'd1;
    end
    assign scl = clk_div;
    
    //状态机
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            sda_oe <= 1'b0; //sda 默认不使能输出(高阻态经上拉为 1)
            sda_out <= 1'b1;//sda 默认输出 1 避免输出 0
            work_en <= 1'b0;
            work_done <= 1'b0;
            cnt_bit <= 4'd0;
        end else case(state)
            //---------------------空闲----------------------//
            IDLE: begin
                sda_oe <= 1'b0;
                sda_out <= 1'b1;
                work_done <= 1'b0;
                if (work_start) begin
                    work_en <= 1'b1; //工作使能信号 work_en(工作时持续为 1)
                    state <= START;
                end
            end
            //--------------------起始位 1--------------------//
            START: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_1) begin
                    sda_out <= 1'b0; //sda 输出起始位 0
                    state <= W_SLAVE_ADDR;
                end
            end
            //--------------7bit 从地址 + 写命令 0---------------//
            W_SLAVE_ADDR: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) begin
                    if (cnt_bit != 4'd8) begin
                        sda_out <= w_slave_addr_buf[7-cnt_bit]; //sda 输出设备地址(从高到低)
                        cnt_bit <= cnt_bit + 4'd1;
                    end else begin
                        state <= ACK1;
                        cnt_bit <= 4'd0;
                    end
                end
            end
            //--------------------应答 1---------------------//
            ACK1: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_ack_jump) state <= W_H_BYTE_ADDR;
            end
            //-----------------高 8bit 字节地址-----------------//
            W_H_BYTE_ADDR: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) begin
                    if (cnt_bit != 4'd8) begin
                        sda_out <= H_byte_addr_buf[7-cnt_bit]; //sda 输出字节地址(从高到低)
                        cnt_bit <= cnt_bit + 4'd1;
                    end else begin
                        state <= ACK2;
                        cnt_bit <= 4'd0;
                    end
                end
            end
            //--------------------应答 2---------------------//
            ACK2: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_ack_jump) state <= W_L_BYTE_ADDR;
            end
            //-----------------低 8bit 字节地址-----------------//
            W_L_BYTE_ADDR: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) begin
                    if (cnt_bit != 4'd8) begin
                        sda_out <= L_byte_addr_buf[7-cnt_bit]; //sda 输出字节地址(从高到低)
                        cnt_bit <= cnt_bit + 4'd1;
                    end else begin
                        state <= ACK3;
                        cnt_bit <= 4'd0;
                    end
                end
            end
            //--------------------应答 3---------------------//
            ACK3: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_ack_jump) begin
                    state <= rw_ctrl ? STOP2 : W_DATA; //读写操作判断(0 写 1 读)
                    sda_out <= rw_ctrl ? 1'b0 : sda_out; //停止位要输出 0 跳 1,转状态时提前置 0
                end
            end
            //--------------------停止位--------------------//
            STOP: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_1) begin
                    sda_out <= 1'b1;
                    work_done <= 1'b1; //工作结束信号置 1(在 STOP 转 IDLE 时清 0)
                    work_en <= 1'b0; //工作使能信号置 0
                    state <= IDLE;
                end
            end
            //----------------写操作特殊状态-----------------
            //-----------------8bit 写入数据-----------------
            W_DATA: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) begin
                    if (cnt_bit != 4'd8) begin
                        sda_out <= w_data_buf[7-cnt_bit]; //sda 输出写入数据(从高到低)
                        cnt_bit <= cnt_bit + 4'd1;
                    end else begin
                        state <= W_ACK;
                        cnt_bit <= 4'd0;
                    end
                end
            end
            //-------------------写应答---------------------//
            W_ACK: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_ack_jump) begin
                    sda_out <= 1'b0; //停止位要输出 0 跳 1,转状态时提前置 0
                    state <= STOP;
                end
            end
            //----------------读操作特殊状态-----------------
            //------------------中间停止位------------------//
            STOP2: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_1) begin
                    sda_out <= 1'b1;
                    state <= START2;
                end
            end
            //-------------------起始位 2--------------------//
            START2: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_1) begin
                    sda_out <= 1'b0; //sda 输出起始位 0
                    state <= R_SLAVE_ADDR;
                end
            end
            //--------------7bit 从地址 + 读命令 1---------------//
            R_SLAVE_ADDR: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) begin
                    if (cnt_bit != 4'd8) begin
                        sda_out <= r_slave_addr_buf[7-cnt_bit]; //sda 输出设备地址(从高到低)
                        cnt_bit <= cnt_bit + 4'd1;
                    end else begin
                        state <= R_ACK;
                        cnt_bit <= 4'd0;
                    end
                end
            end
            //-------------------读应答---------------------//
            R_ACK: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_ack_jump) state <= R_DATA;
            end
            //-----------------8bit 读取数据-----------------
            R_DATA: begin
                sda_oe <= 1'b0; //sda 输出失能作输入
                if (scl_half_1 && cnt_bit!=4'd8) begin
                    r_data_buf[7-cnt_bit] <= sda_in; //sda 在 scl 高电平中点读取数据(从高到低)
                    cnt_bit <= cnt_bit + 4'd1;
                end
                if (scl_ack_jump && cnt_bit==4'd8) begin
                    state <= N_ACK;
                    cnt_bit <= 4'd0;
                    r_data <= r_data_buf; //从寄存器取出读取的数据
                end
            end
            //--------------------无应答--------------------//
            N_ACK: begin
                sda_oe <= 1'b1; //sda 输出使能
                if (scl_half_0) sda_out <= 1'b1;
                if (scl_ack_jump) begin
                    sda_out <= 1'b0; //停止位要输出 0 跳 1,转状态时提前置 0
                    state <= STOP;
                end
            end
            default: state <= IDLE;
        endcase
    end
endmodule
(1.2)ov5640 初始化数据表

有了 SCCB 控制器后,可以实现对 ov5640 寄存器的读写操作了,接下来需要关心如何配置的问题,也就是具体向什么寄存器写什么数据,具体参考 ov5640 的用户手册。

下面展示的为 ov5640 摄像头初始化模代码块,用于配置摄像头寄存器以实现分辨率 640x480 30fps(支持图像大小参数设置)、翻转镜像(支持参数控制)、60fps、RGB565 格式的图像输出。模块通过状态机控制初始化流程,依次完成复位、延时、寄存器配置操作。初始化数据存储在 data 数组中,该模块配合 SCCB 控制器模块可以依次完成 data 中所有数据的写入。初始化完成后输出 initial_done 信号。

//ov5640 寄存器初始化配置参数 : 分辨率 640*480 30fps RGB565 格式
`timescale 1ns / 1ps
module ov5640_initial_table#(
    parameter IMAGE_WIDTH = 16'd640,      //摄像头输出图像宽度
    parameter IMAGE_HEIGHT = 16'd480,     //摄像头输出高度宽度
    parameter IMAGE_FLIP_EN = 1'b0,       //图像翻转使能
    parameter IMAGE_MIRROR_EN = 1'b0      //图像镜像使能
)(
    input wire clk,                       //系统时钟 100MHz
    input wire rst_n,                     //复位
    input wire initial_start,             //初始化启动信号
    input wire work_done,                 //SCCB 控制器写数据完成信号
    output reg work_start,                //SCCB 控制器写数据启动信号
    output reg [23:0] initial_data,       //高 16 位为寄存器地址,低 8 位为写入数据
    output reg initial_done               //初始化完成信号(可输出给 LED 作指示灯:进行初始化时为 0,完成后置 1)
);
    localparam DATA_SIZE = 9'd257;        //初始化数据总数
    localparam DELAY_5ms = 20'd500_000;   //clk 100MHz 延时 5ms 需要计数 500_000 次
    localparam IMAGE_FLIP_DAT = IMAGE_FLIP_EN ? 8'h47 : 8'h41,
               IMAGE_MIRROR_DAT = IMAGE_MIRROR_EN ? 8'h01 : 8'h07;
    localparam IDLE = 2'd0, RESET = 2'd1, DELAY = 2'd2, DATA = 2'd3;
    reg [23:0] data [DATA_SIZE-1:0];      //DATA_SIZE 个 24 位数据,每个存储一个 ov5640 寄存器地址 + 写入数据
    reg [19:0] delay_cnt;                 //延时计数器
    reg [8:0] cnt;                        //数据个数计数器
    reg [1:0] state;                      //当前状态
    
    //***********************状态机***********************//
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            initial_data <= data[0];
            initial_done <= 1'b0;
            work_start <= 1'b0;
            cnt <= 9'd0;
            delay_cnt <= 20'd0;
        end else case (state)
            IDLE: begin
                if (initial_start) begin
                    initial_done <= 1'b0;
                    initial_data <= data[cnt];
                    work_start <= 1'b1;
                    cnt <= cnt + 9'd1;
                end else if (work_done) begin
                    state <= RESET;
                    initial_data <= data[cnt];
                    work_start <= 1'b1;
                    cnt <= cnt + 9'd1;
                end else work_start <= 1'b0;
            end
            RESET: begin
                if (work_done) state <= DELAY;
                else work_start <= 1'b0;
            end
            DELAY: begin
                if (delay_cnt == DELAY_5ms) begin
                    delay_cnt <= 20'd0;
                    state <= DATA;
                    initial_data <= data[cnt];
                    work_start <= 1'b1;
                    cnt <= cnt + 9'd1;
                end else begin
                    delay_cnt <= delay_cnt + 20'd1;
                    work_start <= 1'b0;
                end
            end
            DATA: begin
                if (work_done) begin
                    if (cnt <= DATA_SIZE-1) begin
                        initial_data <= data[cnt];
                        work_start <= 1'b1;
                        cnt <= cnt + 9'd1;
                    end else begin
                        initial_done <= 1'b1;
                        initial_data <= data[0];
                        cnt <= 9'd0;
                        state <= IDLE;
                    end
                end else work_start <= 1'b0;
            end
            default:;
        endcase
    end
    
    //*********************初始化数据表*********************//
    initial begin
        //*******************************************************初始设置
        //15fps VGA YUV output
        // 24MHz input clock, 24MHz PCLK
        data[0] = 24'h3103_11; // system clock from pad, bit[1]
        data[1] = 24'h3008_82; // software reset, bit[7] ********软件复位
        //delay 5ms 软件复位需要延时 5ms
        data[2] = 24'h3008_42; // software power down, bit[6]
        data[3] = 24'h3103_03; // system clock from PLL, bit[1]
        data[4] = 24'h3017_ff; // FREX, Vsync, HREF, PCLK, D[9:6] output enable
        data[5] = 24'h3018_ff; // D[5:0], GPIO[1:0] output enable
        data[6] = 24'h3034_1a; // MIPI 10-bit
        data[7] = 24'h3037_13; // PLL root divider, bit[4], PLL pre-divider, bit[3:0]
        data[8] = 24'h3108_01; // PCLK root divider, bit[5:4], SCLK2x root divider, bit[3:2]
        // SCLK root divider, bit[1:0]
        data[9] = 24'h3630_36;
        data[10] = 24'h3631_0e;
        data[11] = 24'h3632_e2;
        data[12] = 24'h3633_12;
        data[13] = 24'h3621_e0;
        data[14] = 24'h3704_a0;
        data[15] = 24'h3703_5a;
        data[16] = 24'h3715_78;
        data[17] = 24'h3717_01;
        data[18] = 24'h370b_60;
        data[19] = 24'h3705_1a;
        data[20] = 24'h3905_02;
        data[21] = 24'h3906_10;
        data[22] = 24'h3901_0a;
        data[23] = 24'h3731_12;
        data[24] = 24'h3600_08; // VCM control
        data[25] = 24'h3601_33; // VCM control
        data[26] = 24'h302d_60; // system control
        data[27] = 24'h3620_52;
        data[28] = 24'h371b_20;
        data[29] = 24'h471c_50;
        data[30] = 24'h3a13_43; // pre-gain = 1.047x
        data[31] = 24'h3a18_00; // gain ceiling
        data[32] = 24'h3a19_f8; // gain ceiling = 15.5x
        data[33] = 24'h3635_13;
        data[34] = 24'h3636_03;
        data[35] = 24'h3634_40;
        data[36] = 24'h3622_01; // 50/60Hz detection 50/60Hz 灯光条纹过滤
        data[37] = 24'h3c01_34; // Band auto, bit[7]
        data[38] = 24'h3c04_28; // threshold low sum
        data[39] = 24'h3c05_98; // threshold high sum
        data[40] = 24'h3c06_00; // light meter 1 threshold[15:8]
        data[41] = 24'h3c07_08; // light meter 1 threshold[7:0]
        data[42] = 24'h3c08_00; // light meter 2 threshold[15:8]
        data[43] = 24'h3c09_1c; // light meter 2 threshold[7:0]
        data[44] = 24'h3c0a_9c; // sample number[15:8]
        data[45] = 24'h3c0b_40; // sample number[7:0]
        data[46] = 24'h3810_00; // Timing Hoffset[11:8]
        data[47] = 24'h3811_10; // Timing Hoffset[7:0]
        data[48] = 24'h3812_00; // Timing Voffset[10:8]
        data[49] = 24'h3708_64;
        data[50] = 24'h4001_02; // BLC start from line 2
        data[51] = 24'h4005_1a; // BLC always update
        data[52] = 24'h3000_00; // enable blocks
        data[53] = 24'h3004_ff; // enable clocks
        data[54] = 24'h300e_58; // MIPI power down, DVP enable
        data[55] = 24'h302e_00; // 设置输出格式为 RGB 格式
        data[56] = 24'h4300_61; // RGB565
        //h4300_6f(以太网模式参数)
        data[57] = 24'h501f_01; // RGB
        /*原配置
        data[56] = 24'h4300_30; // YUV 422, YUYV
        data[57] = 24'h501f_00; // YUV 422
        */
        data[58] = 24'h440e_00;
        data[59] = 24'h5000_a7; // Lenc on, raw gamma on, BPC on, WPC on, CIP on
        // AEC target 自动曝光控制
        data[60] = 24'h3a0f_30; // stable range in high
        data[61] = 24'h3a10_28; // stable range in low
        data[62] = 24'h3a1b_30; // stable range out high
        data[63] = 24'h3a1e_26; // stable range out low
        data[64] = 24'h3a11_60; // fast zone high
        data[65] = 24'h3a1f_14; // fast zone low
        // Lens correction for ? 镜头补偿
        data[66] = 24'h5800_23;
        data[67] = 24'h5801_14;
        data[68] = 24'h5802_0f;
        data[69] = 24'h5803_0f;
        data[70] = 24'h5804_12;
        data[71] = 24'h5805_26;
        data[72] = 24'h5806_0c;
        data[73] = 24'h5807_08;
        data[74] = 24'h5808_05;
        data[75] = 24'h5809_05;
        data[76] = 24'h580a_08;
        data[77] = 24'h580b_0d;
        data[78] = 24'h580c_08;
        data[79] = 24'h580d_03;
        data[80] = 24'h580e_00;
        data[81] = 24'h580f_00;
        data[82] = 24'h5810_03;
        data[83] = 24'h5811_09;
        data[84] = 24'h5812_07;
        data[85] = 24'h5813_03;
        data[86] = 24'h5814_00;
        data[87] = 24'h5815_01;
        data[88] = 24'h5816_03;
        data[89] = 24'h5817_08;
        data[90] = 24'h5818_0d;
        data[91] = 24'h5819_08;
        data[92] = 24'h581a_05;
        data[93] = 24'h581b_06;
        data[94] = 24'h581c_08;
        data[95] = 24'h581d_0e;
        data[96] = 24'h581e_29;
        data[97] = 24'h581f_17;
        data[98] = 24'h5820_11;
        data[99] = 24'h5821_11;
        data[100] = 24'h5822_15;
        data[101] = 24'h5823_28;
        data[102] = 24'h5824_46;
        data[103] = 24'h5825_26;
        data[104] = 24'h5826_08;
        data[105] = 24'h5827_26;
        data[106] = 24'h5828_64;
        data[107] = 24'h5829_26;
        data[108] = 24'h582a_24;
        data[109] = 24'h582b_22;
        data[110] = 24'h582c_24;
        data[111] = 24'h582d_24;
        data[112] = 24'h582e_06;
        data[113] = 24'h582f_22;
        data[114] = 24'h5830_40;
        data[115] = 24'h5831_42;
        data[116] = 24'h5832_24;
        data[117] = 24'h5833_26;
        data[118] = 24'h5834_24;
        data[119] = 24'h5835_22;
        data[120] = 24'h5836_22;
        data[121] = 24'h5837_26;
        data[122] = 24'h5838_44;
        data[123] = 24'h5839_24;
        data[124] = 24'h583a_26;
        data[125] = 24'h583b_28;
        data[126] = 24'h583c_42;
        data[127] = 24'h583d_ce; // lenc BR offset
        // AWB 自动白平衡
        data[128] = 24'h5180_ff; // AWB B block
        data[129] = 24'h5181_f2; // AWB control
        data[130] = 24'h5182_00; // [7:4] max local counter, [3:0] max fast counter
        data[131] = 24'h5183_14; // AWB advanced
        data[132] = 24'h5184_25;
        data[133] = 24'h5185_24;
        data[134] = 24'h5186_09;
        data[135] = 24'h5187_09;
        data[136] = 24'h5188_09;
        data[137] = 24'h5189_75;
        data[138] = 24'h518a_54;
        data[139] = 24'h518b_e0;
        data[140] = 24'h518c_b2;
        data[141] = 24'h518d_42;
        data[142] = 24'h518e_3d;
        data[143] = 24'h518f_56;
        data[144] = 24'h5190_46;
        data[145] = 24'h5191_f8; // AWB top limit
        data[146] = 24'h5192_04; // AWB bottom limit
        data[147] = 24'h5193_70; // red limit
        data[148] = 24'h5194_f0; // green limit
        data[149] = 24'h5195_f0; // blue limit
        data[150] = 24'h5196_03; // AWB control
        data[151] = 24'h5197_01; // local limit
        data[152] = 24'h5198_04;
        data[153] = 24'h5199_12;
        data[154] = 24'h519a_04;
        data[155] = 24'h519b_00;
        data[156] = 24'h519c_06;
        data[157] = 24'h519d_82;
        data[158] = 24'h519e_38; // AWB control
        // Gamma 伽玛曲线
        data[159] = 24'h5480_01; // Gamma bias plus on, bit[0]
        data[160] = 24'h5481_08;
        data[161] = 24'h5482_14;
        data[162] = 24'h5483_28;
        data[163] = 24'h5484_51;
        data[164] = 24'h5485_65;
        data[165] = 24'h5486_71;
        data[166] = 24'h5487_7d;
        data[167] = 24'h5488_87;
        data[168] = 24'h5489_91;
        data[169] = 24'h548a_9a;
        data[170] = 24'h548b_aa;
        data[171] = 24'h548c_b8;
        data[172] = 24'h548d_cd;
        data[173] = 24'h548e_dd;
        data[174] = 24'h548f_ea;
        data[175] = 24'h5490_1d;
        // color matrix 色彩矩阵
        data[176] = 24'h5381_1e; // CMX1 for Y
        data[177] = 24'h5382_5b; // CMX2 for Y
        data[178] = 24'h5383_08; // CMX3 for Y
        data[179] = 24'h5384_0a; // CMX4 for U
        data[180] = 24'h5385_7e; // CMX5 for U
        data[181] = 24'h5386_88; // CMX6 for U
        data[182] = 24'h5387_7c; // CMX7 for V
        data[183] = 24'h5388_6c; // CMX8 for V
        data[184] = 24'h5389_10; // CMX9 for V
        data[185] = 24'h538a_01; // sign[9]
        data[186] = 24'h538b_98; // sign[8:1]
        // UV adjust UV 色彩饱和度调整
        data[187] = 24'h5580_06; // saturation on, bit[1]
        data[188] = 24'h5583_40;
        data[189] = 24'h5584_10;
        data[190] = 24'h5589_10;
        data[191] = 24'h558a_00;
        data[192] = 24'h558b_f8;
        data[193] = 24'h501d_40; // enable manual offset of contrast
        // CIP 锐化和降噪
        data[194] = 24'h5300_08; // CIP sharpen MT threshold 1
        data[195] = 24'h5301_30; // CIP sharpen MT threshold 2
        data[196] = 24'h5302_10; // CIP sharpen MT offset 1
        data[197] = 24'h5303_00; // CIP sharpen MT offset 2
        data[198] = 24'h5304_08; // CIP DNS threshold 1
        data[199] = 24'h5305_30; // CIP DNS threshold 2
        data[200] = 24'h5306_08; // CIP DNS offset 1
        data[201] = 24'h5307_16; // CIP DNS offset 2
        data[202] = 24'h5309_08; // CIP sharpen TH threshold 1
        data[203] = 24'h530a_30; // CIP sharpen TH threshold 2
        data[204] = 24'h530b_04; // CIP sharpen TH offset 1
        data[205] = 24'h530c_06; // CIP sharpen TH offset 2
        data[206] = 24'h5025_00;
        data[207] = 24'h3008_02; // wake up from standby, bit[6]
        //*******************************************************800x480 预览
        // 800x480 15 帧/秒
        // 800x480 15fps, night mode 5fps
        // input clock 24Mhz, PCLK 45.6Mhz
        data[208] = 24'h3035_41; // PLL
        data[209] = 24'h3036_72; // PLL
        data[210] = 24'h3c07_08; // light meter 1 threshold[7:0]
        // 用参数进行控制镜像和翻转
        data[211] = {16'h3820, IMAGE_FLIP_DAT}; // flip
        data[212] = {16'h3821, IMAGE_MIRROR_DAT}; // mirror(镜像寄存器再最后又写了一次才能写进去,没找到原因,可能是后面其他设置将他初始化了)
        /*原配置
        data[211] = 24'h3820_41; // flip
        data[212] = 24'h3821_07; // mirror
        */
        data[213] = 24'h3814_31; // timing X inc
        data[214] = 24'h3815_31; // timing Y inc
        data[215] = 24'h3800_00; // HS
        data[216] = 24'h3801_00; // HS
        data[217] = 24'h3802_00; // VS
        data[218] = 24'h3803_be; // VS
        data[219] = 24'h3804_0a; // HW (HE)
        data[220] = 24'h3805_3f; // HW (HE)
        data[221] = 24'h3806_06; // VH (VE)
        data[222] = 24'h3807_e4; // VH (VE)
        // 用参数进行定义图像输出大小 (分辨率)
        data[223] = {16'h3808, IMAGE_WIDTH [15:8]}; // DVPHO
        data[224] = {16'h3809, IMAGE_WIDTH [7:0] }; // DVPHO
        data[225] = {16'h380a, IMAGE_HEIGHT[15:8]}; // DVPVO
        data[226] = {16'h380b, IMAGE_HEIGHT[7:0] }; // DVPHO
        /*原配置
        data[223] = 24'h3808_03; // DVPHO (宽度设置 0320h = 800)
        data[224] = 24'h3809_20; // DVPHO
        data[225] = 24'h380a_01; // DVPVO (高度设置 01e0h = 480)
        data[226] = 24'h380b_e0; // DVPVO
        */
        data[227] = 24'h380c_07; // HTS
        data[228] = 24'h380d_69; // HTS
        data[229] = 24'h380e_03; // VTS
        data[230] = 24'h380f_21; // VTS
        data[231] = 24'h3813_06; // timing V offset
        data[232] = 24'h3618_00;
        data[233] = 24'h3612_29;
        data[234] = 24'h3709_52;
        data[235] = 24'h370c_03;
        data[236] = 24'h3a02_09; // 60Hz max exposure, night mode 5fps
        data[237] = 24'h3a03_63; // 60Hz max exposure
        // banding filters are calculated automatically in camera driver
        //data[] = 24'h3a08_00; // B50 step
        //data[] = 24'h3a09_78; // B50 step
        //data[] = 24'h3a0a_00; // B60 step
        //data[] = 24'h3a0b_64; // B60 step
        //data[] = 24'h3a0e_06; // 50Hz max band
        //data[] = 24'h3a0d_08; // 60Hz max band
        data[238] = 24'h3a14_09; // 50Hz max exposure, night mode 5fps
        data[239] = 24'h3a15_63; // 50Hz max exposure
        data[240] = 24'h4004_02; // BLC line number
        data[241] = 24'h3002_1c; // reset JFIFO, SFIFO, JPG
        data[242] = 24'h3006_c3; // disable clock of JPEG2x, JPEG
        data[243] = 24'h4713_03; // JPEG mode 3
        data[244] = 24'h4407_04; // Quantization sacle
        data[245] = 24'h460b_35;
        data[246] = 24'h460c_22;
        data[247] = 24'h4837_22; // MIPI global timing
        data[248] = 24'h3824_02; // PCLK manual divider
        data[249] = 24'h5001_a3; // SDE on, CMX on, AWB on
        data[250] = 24'h3503_00; // AEC/AGC on
        data[251] = {16'h3821, IMAGE_MIRROR_DAT}; // mirror(再写一次,前面由于某种原因没写进去)
        // 800x480 30 帧/秒
        // YUV 800x480 30fps, night mode 5fps
        // Input Clock = 24Mhz, PCLK = 91.2MHz
        // same settings as 800x480 15fps, except the following settings(在 800x480 15fps 基础上修改以下参数)
        data[252] = 24'h3035_21; // PLL
        data[253] = 24'h3a02_12; // 60Hz max exposure, night mode 5fps
        data[254] = 24'h3a03_c6; // 60Hz max exposure
        data[255] = 24'h3a14_12; // 50Hz max exposure, night mode 5fps
        data[256] = 24'h3a15_c6; // 50Hz max exposur
    end
endmodule

(2)DVP 数据采集

配置完寄存器后,摄像头会开始输出图像数据,对于 ov5640 来说一般是通过 DVP 接口进行数据接收采集,需要添加一个地址输出端口,因为图像数据需要依次存入 RAM,代码如下:

 `timescale 1ns / 1ps
module DVP_ctrl#(
    parameter PIC_CNT_MAX = 8'd10 //舍弃前 10 帧不稳定图像数据
)(
    input wire rst_n,
    input wire ov5640_pclk,       //摄像头像素时钟
    input wire ov5640_href,       //摄像头行同步信号
    input wire ov5640_vsync,      //摄像头场同步信号
    input wire [7:0] ov5640_data, //摄像头场数据输入
    output reg [15:0] RGB565_data,//图像数据输出(RGB565 格式)
    output wire data_valid,       //数据有效信号(存储器写使能信号)
    output reg [16:0] w_addr      //数据地址(存储器写数据地址)
);
    reg pix_flag;                 //一像素数据结束标志位
    wire pic_flag;                //一帧图像结束标志位
    reg pic_valid;                //帧有效标志位
    reg [7:0] pic_cnt;            //帧计数器
    reg [7:0] r_ov5640_data;      //输入数据缓存
    reg ov5640_vsync_delay;       //场同步信号打拍
    reg pix_flag_delay;           //一像素数据结束标志位打拍
    
    //***************************** 场同步 ****************************//
    //场同步信号打拍(用于检测 vsync 上升沿)
    always@(posedge ov5640_pclk or negedge rst_n) if(rst_n == 1'b0) ov5640_vsync_delay <= 1'b0; else ov5640_vsync_delay <= ov5640_vsync;
    //一帧图像结束标志位(vsync 上升沿产生一次)
    assign pic_flag = ((ov5640_vsync_delay == 1'b0) && (ov5640_vsync == 1'b1)) ? 1'b1 : 1'b0;
    //前几帧计数,计满产生帧有效信号
    always @(posedge ov5640_pclk or negedge rst_n) begin
        if (!rst_n) begin
            pic_cnt <= 8'd0;
            pic_valid <= 1'b0;
        end else if (pic_flag) begin
            if (pic_cnt == PIC_CNT_MAX) begin
                pic_cnt <= 8'd0;
                pic_valid <= 1'b1;
            end else pic_cnt <= pic_cnt + 8'd1;
        end
    end
    
    //***************************** 行同步 ****************************//
    //行同步
    always @(posedge ov5640_pclk or negedge rst_n) begin
        if (!rst_n) begin
            pix_flag <= 1'b0;
            r_ov5640_data <= 8'b0;
            RGB565_data <= 8'b0;
        end else if (ov5640_href) begin
            if (!pix_flag) begin
                r_ov5640_data <= ov5640_data; //先缓存高 8 位
                pix_flag <= 1'b1;
            end else begin
                RGB565_data <= {r_ov5640_data , ov5640_data}; //后拼接低 8 位输出
                pix_flag <= 1'b0;
            end
        end
    end
    
    //更新地址
    always @(posedge ov5640_pclk or negedge rst_n) begin
        if (!rst_n) w_addr <= 17'h0;
        else if (pic_flag) //一帧结束重置写地址
            w_addr <= 17'h0;
        else if (ov5640_href && pic_valid)
            if (pix_flag) w_addr <= w_addr + 17'h1;
    end
    
    //一像素数据结束标志位打拍(用于产生像素数据有效信号)
    always@(posedge ov5640_pclk or negedge rst_n) if(rst_n == 1'b0) pix_flag_delay <= 1'b0; else pix_flag_delay <= pix_flag;
    //像素数据有效信号
    assign data_valid = pic_valid & pix_flag_delay;
endmodule 

(3)RAM 数据缓存

为什么需要缓存图像数据:

  1. 速率匹配:摄像头输出速率与显示或处理速率可能不一致,缓存可平衡两者间的速度差异。
  2. 数据完整性:缓存确保数据在传输过程中不会丢失或损坏,尤其是在异步系统中。
  3. 帧同步:缓存可以实现帧的完整存储,避免显示或处理时出现撕裂或不完整帧。
  4. 提高效率:还可以通过乒乓操作或双缓冲技术,缓存可以实现读写并行,提高系统吞吐量。

但是该工程只是跑个大致流程,所以就简单用 FPGA 上的 block ram 资源进行缓存,但一般不建议这样做,因为图像数据很大,一般不会 RAM 进行缓存,通常选择 DDR 或者 SDRAM 之类的。我最终要在屏幕上显示的图像的像素大小为 350218(因为 RAM 容量有限就没做显示屏全屏显示),数据格式为 RGB565(16 位),因此需要 RAM 数据位宽 16 位,深度至少为 350218 = 76,300 才能存下一帧数据保证画面完全显示。我的 RAM 配置如下:

文章配图

文章配图

文章配图

(3)VGA 控制器

完成数据采集和缓存后只需要将数据从 RAM 读出来,通过 VGA 控制器输出给显示屏即可。

 `timescale 1ns / 1ps
module VGA_ctrl#(
    parameter DISP_WIDTH = 12'd256, //屏幕显示区域宽度(设置为偶数,最大为分辨率宽 H_DATA)
    parameter DISP_HEIGTH = 12'd160 //屏幕显示区域高度(设置为偶数,最大为分辨率高 V_DATA)
)(
    input wire clk,                 //输入频率参考 VGA 参数表(可用时钟 IP 生成 VGA_clk)
    input wire rst_n,               //复位
    input wire [15:0]data_in,       //数据输入(数据从 RAM 读取)
    output wire hsync,              //行同步信号
    output wire vsync,              //场同步信号
    output reg [15:0]RGB,           //RGB565 格式数据 (Red[15:11] Green[10:5] Blue[4:0])
    output reg [16:0]data_addr      //数据地址(RAM 读数据地址)
);
    reg [11:0] h_cnt;               //水平计数器(行:对应像素点的行位置 x)
    reg [11:0] v_cnt;               //垂直计数器(场:对应像素点的列位置 y)
    wire data_valid;                //数据有效信号(有效数据期间为 1)
    wire [11:0] pix_x;              //像素点 x 坐标
    wire [11:0] pix_y;              //像素点 y 坐标
    wire disp_area;                 //显示区域信号(当像素处于显示区域时为 1)
    
    //640*480@60Hz 25.175MHz VGA 时序参数 (代表各区间持续的时钟周期个数,根据输出分辨率进行设置)
    localparam H_SYNC = 12'd96,     //水平同步脉冲时间
               H_BACK = 12'd40,     //水平后沿时间
               H_LEFT = 12'd8,      //水平左边时间
               H_DATA = 12'd640,    //水平数据输出时间
               H_RIGHT = 12'd8,     //水平右边时间
               H_FRONT = 12'd8,     //水平前沿时间
               H_TOTAL = H_SYNC + H_BACK + H_LEFT + H_DATA + H_RIGHT + H_FRONT, //水平总计时间
               V_SYNC = 12'd2,      //垂直同步脉冲时间
               V_BACK = 12'd25,     //垂直后沿时间时间
               V_LEFT = 12'd8,      //垂直左边时间
               V_DATA = 12'd480,    //垂直数据输出
               V_RIGHT = 12'd8,     //垂直右边时间
               V_FRONT = 12'd2,     //垂直前沿时间
               V_TOTAL = V_SYNC + V_BACK + V_LEFT + V_DATA + V_RIGHT + V_FRONT; //垂直总计时间
    
    //颜色参数 RGB565 格式
    localparam RED = 16'hF800, ORANGE = 16'hFC00, YELLOW = 16'hFFE0, GREEN = 16'h07E0, CYAN = 16'h07FF, BLUE = 16'h001F, PURPPLE = 16'hF81F, BLACK = 16'h0000, WHITE = 16'hFFFF, GRAY = 16'hD69A;
    
    //**********************计数器**********************//
    //水平计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) h_cnt <= 12'd0;
        else if (h_cnt == H_TOTAL-1) //一行结束
            h_cnt <= 12'd0;
        else h_cnt <= h_cnt + 12'd1;
    end
    
    //垂直计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) v_cnt <= 12'd0;
        else if (v_cnt == V_TOTAL-1) //一帧结束
            v_cnt <= 12'd0;
        else if (h_cnt == H_TOTAL-1) //递增条件是一行结束
            v_cnt <= v_cnt + 12'd1;
    end
    
    //*******************生成同步信号*******************//
    assign hsync = (h_cnt < H_SYNC) ? 1'b0 : 1'b1; //输出负脉冲
    assign vsync = (v_cnt < V_SYNC) ? 1'b0 : 1'b1; //输出负脉冲
    //assign hsync = (h_cnt < H_SYNC) ? 1'b1 : 1'b0; //输出正脉冲
    //assign vsync = (v_cnt < V_SYNC) ? 1'b1 : 1'b0; //输出正脉冲
    
    //*******************换算横纵坐标*******************//
    assign data_valid = (h_cnt >= H_SYNC + H_BACK + H_LEFT -1) && (v_cnt >= V_SYNC + V_BACK + V_LEFT -1) && (h_cnt <= H_SYNC + H_BACK + H_LEFT + H_DATA -1) && (v_cnt <= V_SYNC + V_BACK + V_LEFT + V_DATA -1); //有效数据区间
    assign pix_x = (data_valid) ? (h_cnt - (H_SYNC + H_BACK + H_LEFT -1)) : 12'hFFF; //有效坐标范围为 0 ~ H_DATA-1
    assign pix_y = (data_valid) ? (v_cnt - (H_SYNC + H_BACK + H_LEFT -1)) : 12'hFFF; //有效坐标范围为 0 ~ V_DATA-1
    
    //*******************输出像素数据*******************//
    //设置显示区域为中心 DISP_WIDTH * DISP_HIGHT 大小
    assign disp_area = (pix_x >= (H_DATA - DISP_WIDTH )/2 && pix_x < (H_DATA + DISP_WIDTH )/2 && pix_y >= (V_DATA - DISP_HEIGTH)/2 && pix_y < (V_DATA + DISP_HEIGTH)/2 );
    
    //更新数据
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) RGB <= BLACK;
        else if (disp_area)//处于显示区域时更新数据
            RGB <= data_in;
        else RGB <= BLACK;
    end
    
    //更新地址
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) data_addr <= 17'h0;
        else if (v_cnt == V_TOTAL-1)//一帧结束,从头开始
            data_addr <= 17'h0;
        else if (disp_area)//处于显示区域时更新地址
            data_addr <= data_addr + 17'h1;
    end
endmodule 

(4)顶层模块

最后是顶层模块,除去上述介绍到的模块,还有一个按键消抖模块,用于按钮控制摄像头初始化,以及一个时钟 IP 模块,用于生成准确的 VGA 驱动时钟。

 `timescale 1ns / 1ps
module TOP(
    input wire sys_clk,             //系统时钟 100MHz
    input wire ov5640_pclk,         //ov5640 像素时钟
    input wire ov5640_vsync,        //ov5640 场同步信号
    input wire ov5640_href,         //ov5640 行同步信号
    input wire [7:0] ov5640_data,   //ov5640 DVP 数据输入
    input wire rst_n,               //系统复位
    input wire start,               //初始化启动信号
    inout wire sda,                 //双向数据线(inout)
    output wire scl,                //输出时钟线
    output wire initial_done,       //初始化完成信号
    output wire hsync,              //行同步信号
    output wire vsync,              //场同步信号
    output wire[3:0] red,           //红色像素分量
    output wire[3:0] green,         //绿色像素分量
    output wire[3:0] blue           //蓝色像素分量
);
    wire initial_start;             //初始化启动信号
    wire work_start;                //SCCB 启动信号
    wire work_done;                 //SCCB 工作完成信号
    wire [23:0] initial_data;       //初始化数据线
    wire VGA_clk;                   //VGA25.2MHz 时钟
    wire ram_w_en;                  //ram 写使能信号
    wire [16:0] ram_w_addr;         //ram 写数据地址线
    wire [15:0] ram_w_data;         //ram 写数据线
    wire [16:0] ram_r_addr;         //ram 读数据地址线
    wire [15:0] ram_r_data;         //ram 读数据线
    wire [15:0] RGB;                //RGB565 格式数据 (Red[15:11] Green[10:5] Blue[4:0])
    
    assign red = RGB[15:12];        //截取红色分量高 4 位
    assign green = RGB[10:7];       //截取绿色分量高 4 位
    assign blue = RGB[4:1];         //截取蓝色分量高 4 位
    
    ov5640_initial_table#(
        .IMAGE_WIDTH (16'd350),     //摄像头输出图像宽度
        .IMAGE_HEIGHT (16'd218)     //摄像头输出高度宽度
    )ov5640_initial_table(
        .clk (sys_clk),
        .rst_n (rst_n),
        .initial_start (initial_start),
        .work_done (work_done),
        .work_start (work_start),
        .initial_data (initial_data),
        .initial_done (initial_done)
    );
    
    SCCB_ctrl SCCB_ctrl(
        .clk (sys_clk),
        .rst_n (rst_n),
        .sda (sda),
        .scl (scl),
        .work_start (work_start),
        .rw_ctrl (1'b0),
        .slave_addr (7'b0111_100),   //ov5640:0111_100
        .byte_addr (initial_data[23:8]),
        .w_data (initial_data[7:0]),
        .r_data (),
        .work_done (work_done)
    );
    
    DVP_ctrl #(
        .PIC_CNT_MAX (8'd10)         //舍弃前 10 帧图像
    ) DVP_ctrl (
        .ov5640_pclk (ov5640_pclk),
        .rst_n (rst_n),
        .ov5640_vsync (ov5640_vsync),
        .ov5640_href (ov5640_href),
        .ov5640_data (ov5640_data),
        .data_valid (ram_w_en),
        .w_addr (ram_w_addr),
        .RGB565_data (ram_w_data)
    );
    
    VGA_ctrl#(
        .DISP_WIDTH (12'd350),        //屏幕显示区域宽度(设置为偶数,最大为分辨率宽 H_DATA)
        .DISP_HEIGTH (12'd218)        //屏幕显示区域高度(设置为偶数,最大为分辨率高 V_DATA)
    ) VGA_ctrl(
        .clk (VGA_clk),
        .rst_n (rst_n),
        .data_addr (ram_r_addr),
        .data_in (ram_r_data),
        .hsync (hsync),
        .vsync (vsync),
        .RGB (RGB)
    );
    
    key_filter key_filter(
        .i_clk (sys_clk),
        .i_rstn (rst_n),
        .i_key (start),
        .ok (initial_start)           //按键松开时产生一个 clk 周期的高电平脉冲
    );
    
    VGA_clk_gen VGA_clk_gen(
        .sys_clk (sys_clk),
        .VGA_clk (VGA_clk)
    );
    
    RAM RAM (
        .clka (ov5640_pclk),
        .wea (ram_w_en),
        .addra (ram_w_addr),
        .dina (ram_w_data),
        .clkb (VGA_clk),
        .addrb (ram_r_addr),
        .doutb (ram_r_data)
    );
endmodule

三、效果演示

VGA 以 640480@60Hz 驱动显示屏,在中心 350218 区域进行视频显示,不过视频的画面有轻微的撕裂感,看上去像是有一个无形的分界线(即一个画面存在两帧或多帧图像),估计是 RAM 缓存导致的,可能加个乒乓缓存操作就能解决(实测降低 ov5640 输出视频帧数也能解决)。此外右侧画面被部分切割到左侧,估计是帧同步没做好,所以仍有不足。

文章配图

目录

  1. 一、工程介绍
  2. 二、Verilog 实现
  3. (1)OV5640 初始化
  4. (1.1)SCCB 控制器
  5. (1.2)ov5640 初始化数据表
  6. (2)DVP 数据采集
  7. (3)RAM 数据缓存
  8. (3)VGA 控制器
  9. (4)顶层模块
  10. 三、效果演示

更多推荐文章

查看全部
  • Shopee Web SAP 加密算法逆向分析与 RPC 调用实践
  • Qwen3-VL-WEBUI 架构与 Instruct/Thinking 双模式实战
  • 基于 SQL 与 AI 的 ER 图自动生成技术解析
  • Milvus 安装指南:Docker、Docker Compose 与 Kubernetes 部署方式
  • HTML 与 JavaScript 协作机制解析
  • Cursor、Kiro 与 Google Antigravity 深度解析:AI 编程工具新趋势
  • 若依 (RuoYi) 低代码框架深度剖析:架构、优劣与选型
  • AI Skills 详解:概念、区别与配置方法
  • 2026 年人工智能趋势:智能体、元宇宙与商业化落地
  • AIGC 与艺术创作:机遇
  • 腿式机器人 IMU 融合与状态估计实战解析
  • Neo4j 安装与使用教程
  • C++ 多线程进阶:互斥锁解决竞态条件
  • 基于 Python 的 AI 三剑客:文档总结、代码生成与智能检索
  • Stable Diffusion 本地部署与使用指南
  • 2025 年智能机器人操作系统(AGIROS)开源社区生态大会
  • 攻防演练中 DTcms 代码审计与密码解密实战
  • 大语言模型 (LLM) 产品开发流程参考
  • 基于 aiortc 与 Django 构建 WebRTC 实时通信系统
  • LLM Agent 工作流 Prompt 设计精解:规划、反思与工具调用

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online