rk3568安卓13驱动适配-HDMI分流(GSV2001)
本文记录工作中基于rk3568安卓13的SDK适配板载外设GSV2001芯片驱动时具体的修改项和问题目录前言一、GSV2001工作原理梳理二、GSV2001软件适配1.驱动移植1.1 底层接口移植1.2 Main函数移植1.3 设备树文件2.问题记录2.1 I2C读协议错误2.2 edid写入失败总结前言GSV2001 是一款由 GSCoolink基石酷联生产的 HDMI 2.0 芯片‌主要功能是将 1 路 HDMI 信号分配为 2 路输出支持 4K 高清视频传输与音频处理 。‌‌‌本文介绍如何在rk3568安卓13的SDK中适配GSV2001让其能正常工作。一、GSV2001工作原理梳理通过芯片规格书和硬件设计原理可以看出主控通过I2C对GSV芯片的寄存器进行配置使其正常工作。官方有提供应用SDK不过是跑在STM32F030C8裸机上的需要移植到RK3568上当作驱动去运行。从硬件上可以看出工作很简单匹配多个电源后控制复位引脚拉低10ms以上即可复位然后通过I2C对GSV芯片进行轮询配置。二、GSV2001软件适配1.驱动移植1.1 底层接口移植首先需要把底层I2C读写接口还有串口打印移植到linux驱动上去都有现成的接口需要注意I2C读时是起始位设备地址写位ACK寄存器地址高字节ACK寄存器地址低字节ACKReStart设备地址读数据一定要注意那个ReStart信号读的接口如下才能规范int ManI2cRead(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len) { struct i2c_msg msgs[2]; uint8_t reg_buf[2]; int ret; if (!g_i2c_client) return -EINVAL; if (!buf || len 0) { return -EINVAL; } /* 准备寄存器地址大端高字节在前 */ reg_buf[0] (reg_addr 8) 0xFF; reg_buf[1] reg_addr 0xFF; /* 消息1: 写寄存器地址不发送 STOP */ msgs[0].addr g_i2c_client-addr; msgs[0].flags 0; /* 写 */ msgs[0].len 2; msgs[0].buf reg_buf; /* 消息2: 读取数据发送 RESTART STOP */ msgs[1].addr g_i2c_client-addr; msgs[1].flags I2C_M_RD; /* 读 */ msgs[1].len len; msgs[1].buf buf; /* 执行传输: 两个消息之间会自动产生 RESTART */ ret i2c_transfer(g_i2c_client-adapter, msgs, 2); if (ret 0) { dev_err(g_i2c_client-dev, GSV: I2C read failed: dev0x%02x reg0x%04x len%d ret%d\n, dev_addr, reg_addr, len, ret); return ret; } if (ret ! 2) { dev_err(g_i2c_client-dev, GSV: I2C read partial: dev0x%02x reg0x%04x ret%d\n, dev_addr, reg_addr, ret); return -EIO; } return len; }SDK中的I2C源文件用普通IO模拟的这里可以删除掉只需要改写bsp.c文件中的接口注释掉不用的接口即可如下/** * file bsp.c * * brief sample bsp support */ #include bsp.h #include linux/i2c.h #include linux/delay.h static struct i2c_client *g_i2c_client NULL; /* 在驱动probe中调用此函数设置client */ void i2c_set_client(struct i2c_client *client) { g_i2c_client client; } int ManI2cRead(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len) { struct i2c_msg msgs[2]; uint8_t reg_buf[2]; int ret; if (!g_i2c_client) return -EINVAL; if (!buf || len 0) { return -EINVAL; } /* 准备寄存器地址大端高字节在前 */ reg_buf[0] (reg_addr 8) 0xFF; reg_buf[1] reg_addr 0xFF; /* 消息1: 写寄存器地址不发送 STOP */ msgs[0].addr g_i2c_client-addr; msgs[0].flags 0; /* 写 */ msgs[0].len 2; msgs[0].buf reg_buf; /* 消息2: 读取数据发送 RESTART STOP */ msgs[1].addr g_i2c_client-addr; msgs[1].flags I2C_M_RD; /* 读 */ msgs[1].len len; msgs[1].buf buf; /* 执行传输: 两个消息之间会自动产生 RESTART */ ret i2c_transfer(g_i2c_client-adapter, msgs, 2); if (ret 0) { dev_err(g_i2c_client-dev, GSV: I2C read failed: dev0x%02x reg0x%04x len%d ret%d\n, dev_addr, reg_addr, len, ret); return ret; } if (ret ! 2) { dev_err(g_i2c_client-dev, GSV: I2C read partial: dev0x%02x reg0x%04x ret%d\n, dev_addr, reg_addr, ret); return -EIO; } return len; } int ManI2cWrite(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len) { uint8_t tx_buf[512]; int i; if (!g_i2c_client) return -EINVAL; if (len 509){ dev_info(g_i2c_client-dev, ManI2cWrite fail overSize\n); return -EINVAL; } tx_buf[0] (reg_addr 8) 0xFF; tx_buf[1] reg_addr 0xFF; for (i 0; i len; i) { tx_buf[2 i] buf[i]; } return i2c_master_send(g_i2c_client, tx_buf, len 2); } /** * brief bsp i2c read function, support bus/dev address, 8/16 register address * return AvOk - success */ AvRet BspI2cRead(uint32 devAddress, uint32 regAddress, uint8 *data, uint16 count) { AvRet ret AvOk; int rst 0; uint8 deviceAddress (uint8)AvGetI2cDeviceAddress(devAddress); uint16 regAdress (uint32)((AvGetRegAddress(devAddress)8) | AvGetRegAddress(regAddress)); rst ManI2cRead(deviceAddress, regAdress, data, count); if(rst 0){ ret rst; } else if(rst 0){ ret AvError; } return ret; } /** * brief bsp i2c write function, support bus/dev address, 8/16 register address * return AvOk - success */ AvRet BspI2cWrite(uint32 devAddress, uint32 regAddress, uint8 *data, uint16 count) { AvRet ret AvOk; int rst 0; char buf[300]; char print_buf[512] {0}; int offset 0; int i 0; uint8 deviceAddress (uint8)AvGetI2cDeviceAddress(devAddress); uint16 regAdress (uint32)((AvGetRegAddress(devAddress)8) | AvGetRegAddress(regAddress)); rst ManI2cWrite(deviceAddress, regAdress, data, count); if(rst 0 ){ ret rst; } else if(rst 0){ ret AvError; } return ret; } /** * brief send one byte from uart * return AvOk - success */ AvRet BspUartSendByte(uint8 *data, uint16 size) { char buf[256]; int i, len; // 1. 检查 g_i2c_client 是否有效 if (!g_i2c_client) { printk(KERN_INFO GSV: UART send %d bytes (client not ready)\n, size); return AvOk; } // 2. 检查数据有效性 if (!data || size 0) { dev_dbg(g_i2c_client-dev, GSV: UART send empty\n); return AvOk; } // 3. 只打印可见字符 len min((int)size, 255); for (i 0; i len; i) { uint8 c data[i]; if (c 0x20 c 0x7F) { buf[i] c; } else if (c \n || c \r) { buf[i] c; } else { buf[i] .; } } buf[i] \0; dev_info(g_i2c_client-dev, GSV: %s\n, buf); return AvOk; } /** * brief get one byte from uart * return AvOk - success */ AvRet BspUartGetByte(uint8 *data) { AvRet ret AvOk; return ret; } /** * brief get current time in ms * return AvOk - success */ AvRet BspGetMilliSecond(uint32 *ms) { *ms (uint32)(ktime_get_real_ns() / 1000000); return AvOk; } AvRet BspGetKey(uint8 *data) { AvRet ret AvNotAvailable; return ret; } AvRet BspIrdaGetByte(uint8 *data) { AvRet ret AvOk; return ret; }1.2 Main函数移植将官方提供的av_main.c文件中的main函数改写成linux驱动中的工作队列形式定时执行。修改后的av_main.c文件如下/** * file av_main.c * * brief sample main entry for audio/video based software */ #include av_main.h #include global_var.h #include linux/module.h #include linux/i2c.h #include linux/delay.h #include linux/workqueue.h #include linux/of.h #include linux/gpio/consumer.h #include linux/of_gpio.h #include linux/gpio.h struct gsv_device_data { struct gpio_desc *reset_gpio; struct i2c_client *client; }; static struct i2c_client *gsv_client; static struct delayed_work gsv_work; static bool gsv_running false; /* 2. Device Level Declaration */ /* 2.1 total devices */ /* it must be declared in AvDevice */ static AvDevice devices[1] {0}; static AvPort gsv2k1Ports[7] {0}; static Gsv2k1Device gsv2k1_0 {0}; extern void i2c_set_client(struct i2c_client *client); static void gsv_work_handler(struct work_struct *work) { if (!gsv_running) return; // 调用官方SDK主循环 AvApiUpdate(); AvPortConnectUpdate(devices[0]); // 重新调度 - 50ms轮询 schedule_delayed_work(gsv_work, msecs_to_jiffies(50)); } static int gsv_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev client-dev; /* 2.2 specific devices and ports */ /* they must be able to be linked to the device in 1. */ dev_info(dev, GSV2001 probe start\n); gsv_client client; i2c_set_client(client); /* 1. Low Level Hardware Level Initialization */ /* 1.1 init bsp support (user speficic) */ // BspInit(); /* 1.2 init software package and hookup users bsp functions */ AvApiInit(); AvApiHookBspFunctions(BspI2cRead, BspI2cWrite, BspUartSendByte, BspUartGetByte, BspGetMilliSecond, BspGetKey, BspIrdaGetByte); AvApiHookUserFunctions(ListenToKeyCommand, ListenToUartCommand, ListenToIrdaCommand); /* 2.3 init device address in 2.2 */ gsv2k1_0.DeviceAddress AvGenerateDeviceAddress(0x00,0x01,0xB0,0x00); /* 2.4 connect devices to device declaration */ AvApiAddDevice(devices[0], Gsv2k1, 0, (void *)gsv2k1_0, (void *)gsv2k1Ports[0], NULL); /* 3. Port Level Declaration */ /* 3.1 init devices and port structure, must declare in number order */ /* 0-3 HdmiRx, 4-7 HdmiTx, 8-9 TTLTx, 10-11 TTLRx, 20-23 Scaler, 24-27 Color, 28 VideoGen, 30 VideoIn, 32 VideoOut, 34 AudioGen, 36 ClockGen */ AvApiAddPort(devices[0],gsv2k1Ports[0] ,0 ,HdmiRx); AvApiAddPort(devices[0],gsv2k1Ports[1] ,4 ,HdmiTx); AvApiAddPort(devices[0],gsv2k1Ports[2] ,5 ,HdmiTx); AvApiAddPort(devices[0],gsv2k1Ports[3] ,8 ,LogicAudioTx); AvApiAddPort(devices[0],gsv2k1Ports[4] ,20,VideoScaler); AvApiAddPort(devices[0],gsv2k1Ports[5] ,24,VideoColor); AvApiAddPort(devices[0],gsv2k1Ports[6] ,10,LogicAudioRx); // AvApiAddPort(devices[0],gsv2k1Ports[6], 28,VideoGen); // AvApiAddPort(devices[0],gsv2k1Ports[8] ,34,AudioGen); // AvApiAddPort(devices[0],gsv2k1Ports[9] ,36,ClockGen); /* 3.2 initialize port content */ #if AvEnableCecFeature gsv2k1Ports[1].content.cec-CecEnable 1; if(AudioStatus 0) gsv2k1Ports[1].content.cec-EnableAudioAmplifier AV_CEC_AMP_TO_DISABLE; else { gsv2k1Ports[1].content.cec-EnableAudioAmplifier AV_CEC_AMP_TO_ENABLE; gsv2k1Ports[1].content.cec-EnableARC AV_CEC_ARC_TO_INITIATE; } Cec_Tx_Audio_Status.Volume 30; Cec_Tx_Audio_Status.Mute 0; /* */ Cec_Tx_Audio_Status.AudioMode 1; /* Audio Mode is ON to meet ARC */ Cec_Tx_Audio_Status.AudioRate 1; /* 100% rate */ Cec_Tx_Audio_Status.AudioFormatCode AV_AUD_FORMAT_LINEAR_PCM; /* Follow Spec */ Cec_Tx_Audio_Status.MaxNumberOfChannels 2; /* Max Channels */ Cec_Tx_Audio_Status.AudioSampleRate 0x07; /* 32KHz/44.1KHz/48KHz */ Cec_Tx_Audio_Status.AudioBitLen 0x01; /* 16-bit only */ Cec_Tx_Audio_Status.MaxBitRate 0; /* default */ Cec_Tx_Audio_Status.ActiveSource 0; /* default */ #endif //------------------------- /* 3.3 init fsms */ AvApiInitDevice(devices[0]); AvApiPortStart(); /* 3.4 routing */ /* connect the port by video using AvConnectVideo */ /* connect the port by audio using AvConnectAudio */ /* connect the port by video and audio using AvConnectAV */ /* 3.4.1 video routing */ /* case 1: default routing RxA-TxA/TxB */ AvApiConnectPort(gsv2k1Ports[0], gsv2k1Ports[1], AvConnectAV); // AvApiConnectPort(gsv2k1Ports[0], gsv2k1Ports[2], AvConnectAV); AvApiConnectPort(gsv2k1Ports[0], gsv2k1Ports[3], AvConnectAudio); ///* case 2: audio insertion */ //AvApiConnectPort(gsv2k1Ports[0], gsv2k1Ports[1], AvConnectVideo); //AvApiConnectPort(gsv2k1Ports[0], gsv2k1Ports[2], AvConnectVideo); //AvApiConnectPort(gsv2k1Ports[9], gsv2k1Ports[1], AvConnectAudio); //AvApiConnectPort(gsv2k1Ports[9], gsv2k1Ports[2], AvConnectAudio); /* case 3: videogen and audiogen */ //AvApiConnectPort(gsv2k1Ports[6], gsv2k1Ports[1], AvConnectVideo); //AvApiConnectPort(gsv2k1Ports[6], gsv2k1Ports[2], AvConnectVideo); //AvApiConnectPort(gsv2k1Ports[7], gsv2k1Ports[1], AvConnectAudio); //AvApiConnectPort(gsv2k1Ports[7], gsv2k1Ports[2], AvConnectAudio); /* 3.4.2 ARC Connection, set after rx port connection to avoid conflict */ #if AvEnableCecFeature if(AudioStatus 1) { AvApiConnectPort(gsv2k1Ports[1], gsv2k1Ports[3], AvConnectAudio); } #endif /* 3.4.3 Internal Video Generator*/ #if AvEnableInternalVideoGen //gsv2k1Ports[6].content.video-timing.Vic 0x61; /* 4K60 */ gsv2k1Ports[6].content.video-timing.Vic 0x60; gsv2k1Ports[6].content.video-AvailableVideoPackets AV_BIT_AV_INFO_FRAME; gsv2k1Ports[6].content.video-Cd AV_CD_24; gsv2k1Ports[6].content.video-Y AV_Y2Y1Y0_RGB; gsv2k1Ports[6].content.vg-Pattern AV_PT_COLOR_BAR; #endif /* 3.4.4 Audio Insertion */ #if AvEnableAudioTTLInput gsv2k1Ports[3].content.audio-AudioMute 0; gsv2k1Ports[3].content.audio-AudFormat AV_AUD_I2S; gsv2k1Ports[3].content.audio-AudType AV_AUD_TYPE_ASP; gsv2k1Ports[3].content.audio-AudCoding AV_AUD_FORMAT_LINEAR_PCM; gsv2k1Ports[3].content.audio-AudMclkRatio AV_MCLK_256FS; gsv2k1Ports[3].content.audio-Layout 0; /* 2 channel Layout 0 */ gsv2k1Ports[3].content.audio-Consumer 0; /* Consumer */ gsv2k1Ports[3].content.audio-Copyright 0; /* Copyright asserted */ gsv2k1Ports[3].content.audio-Emphasis 0; /* No Emphasis */ gsv2k1Ports[3].content.audio-CatCode 0; /* Default */ gsv2k1Ports[3].content.audio-SrcNum 0; /* Refer to Audio InfoFrame */ gsv2k1Ports[3].content.audio-ChanNum 2; /* Audio Channel Count */ gsv2k1Ports[3].content.audio-SampFreq AV_AUD_FS_48KHZ; /* Sample Frequency */ gsv2k1Ports[3].content.audio-ClkAccur 0; /* Level 2 */ gsv2k1Ports[3].content.audio-WordLen 0x0B; /* 24-bit word length */ #endif // 8. 启动工作队列 gsv_running true; INIT_DELAYED_WORK(gsv_work, gsv_work_handler); schedule_delayed_work(gsv_work, msecs_to_jiffies(100)); dev_info(dev, GSV2001 driver loaded\n); return 0; } static int gsv_remove(struct i2c_client *client) { gsv_running false; cancel_delayed_work_sync(gsv_work); dev_info(client-dev, GSV2001 removed\n); return 0; } static const struct of_device_id gsv_of_match[] { { .compatible gsv,gsv2001 }, {} }; MODULE_DEVICE_TABLE(of, gsv_of_match); static struct i2c_driver gsv_driver { .driver { .name gsv2001, .of_match_table gsv_of_match, }, .probe gsv_probe, .remove gsv_remove, }; module_i2c_driver(gsv_driver); MODULE_LICENSE(GPL v2); MODULE_AUTHOR(board); MODULE_DESCRIPTION(GSV2001 HDMI Splitter Driver for RK3568);1.3 设备树文件在板级设备树文件中配置I2C需要注意从源码SDK中能看出设备地址是0x58这里是7位和I2C的协议有关7位地址位第八位是读写位也就是读的话是0xB1,写的话是0xB0,linux底层中I2C的接口都给处理好了。i2c3 { status okay; gsv2001: gsv200158 { compatible gsv,gsv2001; reg 0x58; reset-gpios gpio0 RK_PB6 GPIO_ACTIVE_LOW; status okay; }; };2.问题记录这里记录我调试过程中遇到的问题2.1 I2C读协议错误一开始用以下接口实现的I2C读导致restart信号变成了stop信号gsv芯片不响应。是通过示波器抓I2C总线上的波形对比gsv正常I2C通信才发现的这个错误gsv规格书中关于I2C时序也有列出这点。/* 从 16位 寄存器读一个字节 */ int gsv_i2c_read_reg(u32 dev_addr, u16 reg, u8 *val) { u8 buf[2]; buf[0] (reg 8) 0xFF; buf[1] reg 0xFF; if (i2c_master_send(g_client, buf, 2) 0) return -EIO; return i2c_master_recv(g_client, val, 1); }2.2 edid写入失败编写写接口时会申请tx_buf数组一般我们都喜欢申请256但GSV芯片驱动程序中会写edid加上寄存器地址长度正好257刚好超界导致写入失败。这里需要申请大一些。int ManI2cWrite(uint8_t dev_addr, uint16_t reg_addr, uint8_t *buf, int len) { uint8_t tx_buf[512]; int i; if (!g_i2c_client) return -EINVAL; if (len 509){ dev_info(g_i2c_client-dev, ManI2cWrite fail overSize\n); return -EINVAL; } tx_buf[0] (reg_addr 8) 0xFF; tx_buf[1] reg_addr 0xFF; for (i 0; i len; i) { tx_buf[2 i] buf[i]; } return i2c_master_send(g_i2c_client, tx_buf, len 2); }总结本文介绍了本人在工作过程中适配GSV2001芯片驱动时具体的修改项和遇到的问题为gsv同系列芯片在linux下适配提供了参考。程序包下载链接如下已测试可用。https://download.csdn.net/download/x150061/93278461

相关新闻

Linux系统最新版Docker安装与配置全流程指南

Linux系统最新版Docker安装与配置全流程指南

1. 项目概述:为什么需要一篇“最新版”Docker安装指南?如果你在Linux上折腾过Docker,大概率遇到过这样的场景:兴冲冲地打开官方文档,照着步骤一路敲命令,结果在某个环节卡住,报错信息看得一头雾…

2026/9/22 0:21:23 阅读更多 →
FreeSWITCH呼叫流程全解析:从SIP信令到媒体协商的实战指南

FreeSWITCH呼叫流程全解析:从SIP信令到媒体协商的实战指南

1. 项目概述:从“fs呼叫流程”说起 最近在调试一个基于FreeSWITCH的通信项目,又遇到了那个老生常谈但又无比核心的问题——呼叫流程。无论是刚接触的新手,还是像我这样摸爬滚打多年的老鸟,一旦涉及到SIP协议栈的交互、媒体协商或是…

2026/9/19 14:09:15 阅读更多 →
OpenClaw智能体框架:基于大语言模型的自动化工作流实战指南

OpenClaw智能体框架:基于大语言模型的自动化工作流实战指南

1. 项目概述:OpenClaw (Moltbot) 是什么? 如果你最近在关注AI智能体或者自动化工作流,大概率已经听过 OpenClaw 或者 Moltbot 这两个名字了。简单来说,OpenClaw是一个开源的、基于大语言模型的智能体(Agent&#x…

2026/9/18 1:34:40 阅读更多 →

最新新闻

3个高频坑让你少走弯路:applicable属性新手避坑指南

3个高频坑让你少走弯路:applicable属性新手避坑指南

3个高频坑让你少走弯路:applicable属性新手避坑指南 官方文档那一长串 applicable 定义,看两遍就晕了?别急,这不是你的问题。 很多新手在写权限控制或状态标记时,被 applicable 这个单词卡住。它不像 valid…

2026/9/22 4:03:27 阅读更多 →
5道金采网官网高频面试题:搞定StackTrace报错

5道金采网官网高频面试题:搞定StackTrace报错

5道金采网官网高频面试题:搞定StackTrace报错 面试时最怕什么?不是算法,而是环境配置和报错。 看着满屏红色的 StackTrace,脑子瞬间空白。 这不仅是技术坑,更是金采网官网相关岗位的 高频面试题 核心。…

2026/9/22 4:02:27 阅读更多 →
地城之光源码图解原理:3个致命坑让API全变

地城之光源码图解原理:3个致命坑让API全变

地城之光源码图解原理:3个致命坑让API全变 刚接手《地城之光》旧项目,版本一升级,API 直接炸了。 我盯着满屏的 404 和 Type Error ,头都大了。 别再盲目改代码了,得先搞懂这背后的 图解原理 。…

2026/9/22 4:02:27 阅读更多 →
3个高频面试题拆解printscreen实战,别再只背语法了

3个高频面试题拆解printscreen实战,别再只背语法了

3个高频面试题拆解printscreen实战,别再只背语法了 是不是刚背完 print(screen) 或者 print(screen.buffer)…

2026/9/22 4:02:27 阅读更多 →
廖雪峰git教程避坑指南:从报错到性能优化实战

廖雪峰git教程避坑指南:从报错到性能优化实战

廖雪峰git教程避坑指南:从报错到性能优化实战 盯着屏幕上一长串红色的 Error Trace,是不是感觉大脑瞬间宕机?那些看似天书的英文报错,往往只因为一个拼写错误或者权限缺失。别慌,作为过来人,我深知这种在廖雪峰git教程里卡壳的绝望感…

2026/9/22 4:02:27 阅读更多 →
jinjia进阶用法

jinjia进阶用法

Jinja2与Mako模板引擎深度对比:3个完整示例解决版本升级API变更难题 刚把项目从 Jinja2 2.x 升级到 3.x,或者从 Mako 迁移过来,发现 {{ variable }} 里的过滤器写法变了, {% extends…

2026/9/22 4:01:26 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →