嵌入式C++驱动开发:从硬件抽象到设计模式实践
1. 嵌入式C驱动开发概述在嵌入式系统开发领域驱动开发一直是最具挑战性也最核心的技术环节。不同于应用层开发驱动开发需要开发者同时具备硬件接口操作能力和系统级编程思维。而C作为面向对象的高级语言在嵌入式驱动开发中的应用正变得越来越广泛。传统嵌入式驱动开发主要使用C语言但随着系统复杂度提升C的封装性、类型安全和面向对象特性使其成为更优选择。特别是在需要管理复杂硬件状态、实现设备抽象层或支持多态行为的场景下C能显著提高代码的可维护性和扩展性。典型的嵌入式C驱动开发场景包括微控制器外设驱动GPIO、UART、I2C、SPI等传感器接口驱动加速度计、陀螺仪、环境传感器等显示设备驱动LCD、OLED等存储设备驱动Flash、EEPROM等通信协议栈实现BLE、LoRa、Zigbee等2. 嵌入式C驱动开发环境搭建2.1 工具链选择嵌入式C开发环境与桌面开发有很大不同需要考虑交叉编译、目标平台限制等因素。以下是推荐的开发工具链编译器选择ARM架构arm-none-eabi-gRISC-V架构riscv-none-elf-g其他架构根据芯片厂商推荐选择构建系统CMake推荐跨平台支持好生态完善Makefile轻量级适合简单项目厂商专用IDE如Keil、IAR适合快速原型开发调试工具OpenOCD开源调试工具J-Link/GDB硬件调试组合Trace32高端调试工具商业软件提示嵌入式C开发通常需要关闭或限制某些C特性如RTTI、异常处理等以减少代码体积和运行时开销。2.2 开发环境配置示例以下是一个典型的嵌入式C开发环境配置以STM32为例# 安装工具链 sudo apt install arm-none-eabi-gcc arm-none-eabi-g cmake make # 创建CMakeLists.txt cmake_minimum_required(VERSION 3.12) project(EmbeddedDriver LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # 禁用不需要的C特性 add_compile_options(-fno-exceptions -fno-rtti -ffunction-sections -fdata-sections) # 链接器配置 add_link_options(-Wl,--gc-sections -specsnano.specs -specsnosys.specs)3. 嵌入式C驱动设计模式3.1 硬件抽象层设计在嵌入式C驱动开发中硬件抽象层HAL设计是关键。以下是一个典型的GPIO驱动抽象示例class GpioInterface { public: virtual ~GpioInterface() default; virtual void setDirection(Direction dir) 0; virtual void write(Value val) 0; virtual Value read() const 0; enum class Direction { Input, Output }; enum class Value { Low, High }; }; class Stm32Gpio : public GpioInterface { public: Stm32Gpio(GPIO_TypeDef* port, uint16_t pin) : port_(port), pin_(pin) {} void setDirection(Direction dir) override { GPIO_InitTypeDef init {0}; init.Pin pin_; init.Mode (dir Direction::Input) ? GPIO_MODE_INPUT : GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(port_, init); } // 其他成员函数实现... private: GPIO_TypeDef* port_; uint16_t pin_; };这种设计允许在不改变上层应用代码的情况下更换硬件平台只需提供新的具体实现即可。3.2 策略模式在驱动中的应用策略模式非常适合处理不同硬件实现或算法选择。以下是一个SPI通信的策略模式示例class SpiStrategy { public: virtual ~SpiStrategy() default; virtual uint8_t transfer(uint8_t data) 0; }; class HardwareSpi : public SpiStrategy { public: explicit HardwareSpi(SPI_HandleTypeDef* hspi) : hspi_(hspi) {} uint8_t transfer(uint8_t data) override { uint8_t rxData; HAL_SPI_TransmitReceive(hspi_, data, rxData, 1, HAL_MAX_DELAY); return rxData; } private: SPI_HandleTypeDef* hspi_; }; class SoftwareSpi : public SpiStrategy { public: SoftwareSpi(GpioInterface sck, GpioInterface mosi, GpioInterface miso, GpioInterface cs) : sck_(sck), mosi_(mosi), miso_(miso), cs_(cs) {} uint8_t transfer(uint8_t data) override { uint8_t rxData 0; cs_.write(GpioInterface::Value::Low); for (int i 0; i 8; i) { sck_.write(GpioInterface::Value::Low); mosi_.write((data (1 (7 - i))) ? GpioInterface::Value::High : GpioInterface::Value::Low); sck_.write(GpioInterface::Value::High); if (miso_.read() GpioInterface::Value::High) { rxData | (1 (7 - i)); } } cs_.write(GpioInterface::Value::High); return rxData; } private: GpioInterface sck_; GpioInterface mosi_; GpioInterface miso_; GpioInterface cs_; };4. 嵌入式C驱动开发实战4.1 中断驱动设计嵌入式驱动经常需要处理硬件中断。在C中可以使用回调机制实现优雅的中断处理class ButtonDriver { public: using Callback std::functionvoid(); void setPressedCallback(Callback cb) { pressedCallback_ std::move(cb); } void handleInterrupt() { if (isPressed()) { if (pressedCallback_) { pressedCallback_(); } } } private: bool isPressed() const { // 实际硬件读取逻辑 return HAL_GPIO_ReadPin(BTN_PORT, BTN_PIN) GPIO_PIN_RESET; } Callback pressedCallback_; }; // 中断处理函数C接口 extern C void EXTI0_IRQHandler() { static ButtonDriver button; button.handleInterrupt(); __HAL_GPIO_EXTI_CLEAR_IT(BTN_PIN); }4.2 DMA驱动实现DMA直接内存访问是提高嵌入式系统性能的关键技术。以下是C实现的DMA缓冲区管理template typename T, size_t Size class DmaBuffer { public: DmaBuffer() { // 确保缓冲区对齐DMA通常有对齐要求 static_assert(alignof(T) 4, DMA buffer requires minimum 4-byte alignment); static_assert(sizeof(T) * Size % 4 0, DMA buffer size must be multiple of 4 bytes); } void startTransfer(DMA_HandleTypeDef* hdma) { if (active_) return; SCB_CleanDCache_by_Addr(reinterpret_castuint32_t*(buffer_), sizeof(T) * Size); HAL_DMA_Start_IT(hdma, reinterpret_castuint32_t(srcAddr_), reinterpret_castuint32_t(buffer_), sizeof(T) * Size / 4); active_ true; } bool isTransferComplete() const { return !active_; } T operator[](size_t index) { return buffer_[index]; } const T operator[](size_t index) const { return buffer_[index]; } void handleDmaInterrupt() { if (active_) { SCB_InvalidateDCache_by_Addr(reinterpret_castuint32_t*(buffer_), sizeof(T) * Size); active_ false; } } private: alignas(4) T buffer_[Size]; const void* srcAddr_ nullptr; // 实际应用中设置源地址 bool active_ false; };5. 性能优化与资源管理5.1 内存管理策略嵌入式系统通常资源有限需要特别关注内存使用静态内存分配// 使用std::array代替动态数组 std::arrayuint8_t, 1024 buffer; // 使用placement new在预分配内存上构造对象 alignas(MyClass) uint8_t storage[sizeof(MyClass)]; auto* obj new(storage) MyClass();自定义内存池template typename T, size_t PoolSize class ObjectPool { public: template typename... Args T* create(Args... args) { if (freeList_ nullptr) return nullptr; auto* obj freeList_; freeList_ freeList_-next; new(obj) T(std::forwardArgs(args)...); return obj; } void destroy(T* obj) { obj-~T(); obj-next freeList_; freeList_ obj; } private: union Node { T object; Node* next; }; Node pool_[PoolSize]; Node* freeList_ pool_; ObjectPool() { for (size_t i 0; i PoolSize - 1; i) { pool_[i].next pool_[i 1]; } pool_[PoolSize - 1].next nullptr; } };5.2 实时性保障技术嵌入式驱动通常有严格的实时性要求禁止动态内存分配避免在中断上下文中使用new/delete使用静态分配或内存池临界区保护class CriticalSection { public: CriticalSection() { __disable_irq(); } ~CriticalSection() { __enable_irq(); } CriticalSection(const CriticalSection) delete; CriticalSection operator(const CriticalSection) delete; };延迟处理机制template size_t QueueSize class DeferredWorkQueue { public: using WorkItem std::functionvoid(); bool addWork(WorkItem item) { CriticalSection cs; if (count_ QueueSize) return false; queue_[(head_ count_) % QueueSize] std::move(item); count_; return true; } void process() { while (count_ 0) { WorkItem item; { CriticalSection cs; item std::move(queue_[head_]); head_ (head_ 1) % QueueSize; --count_; } item(); } } private: WorkItem queue_[QueueSize]; size_t head_ 0; size_t count_ 0; };6. 测试与调试技巧6.1 单元测试策略嵌入式C驱动可以通过以下方法进行测试硬件模拟测试class MockGpio : public GpioInterface { public: void setDirection(Direction dir) override { lastDirection_ dir; } Direction lastDirection() const { return lastDirection_; } // 其他模拟实现... private: Direction lastDirection_; }; TEST(GpioDriverTest, SetsCorrectDirection) { MockGpio mock; SomeDriver driver(mock); driver.init(); EXPECT_EQ(mock.lastDirection(), GpioInterface::Direction::Output); }硬件在环测试使用开发板测试夹具进行自动化测试结合脚本控制电源和信号发生器6.2 调试技巧日志输出class DebugLogger { public: template typename... Args static void log(const char* format, Args... args) { char buffer[128]; snprintf(buffer, sizeof(buffer), format, args...); HAL_UART_Transmit(huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); } }; #define LOG_DEBUG(fmt, ...) DebugLogger::log([DEBUG] fmt \r\n, ##__VA_ARGS__)故障注入测试class FaultInjector { public: static void injectGpioReadError() { injectError_ true; } static bool shouldInjectError() { return injectError_; } private: static bool injectError_; }; // 在驱动代码中检查 Value readGpio() { if (FaultInjector::shouldInjectError()) { return Value::High; // 注入错误值 } // 正常读取逻辑 }7. 现代C特性在嵌入式驱动中的应用7.1 constexpr与编译时计算constexpr uint32_t calculateBaudRate(uint32_t peripheralClock, uint32_t desiredBaud) { return peripheralClock / desiredBaud; } class UartConfig { public: static constexpr uint32_t DefaultBaud 115200; constexpr UartConfig(uint32_t clockFreq, uint32_t baud DefaultBaud) : baudReg_(calculateBaudRate(clockFreq, baud)) {} uint32_t baudRegisterValue() const { return baudReg_; } private: uint32_t baudReg_; }; // 编译时计算波特率寄存器值 constexpr auto config UartConfig{84000000, 115200}; static_assert(config.baudRegisterValue() 730, Baud rate calculation error);7.2 模板元编程在驱动中的应用template typename Register, typename BitField class RegisterAccessor { public: static void set() { Register::get() | BitField::mask(); } static void clear() { Register::get() ~BitField::mask(); } static bool isSet() { return (Register::get() BitField::mask()) ! 0; } }; // 定义寄存器结构 struct GpioDataReg { static volatile uint32_t get() { return *reinterpret_castuint32_t*(0x40020000); } }; struct Led1Bit { static constexpr uint32_t mask() { return 0x00000001; } }; using Led1 RegisterAccessorGpioDataReg, Led1Bit; // 使用方式 Led1::set(); // 等同于 GPIO-DATA | 0x018. 嵌入式C驱动开发最佳实践资源获取即初始化(RAII)class SpiTransaction { public: explicit SpiTransaction(SPI_HandleTypeDef* hspi) : hspi_(hspi) { HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET); } ~SpiTransaction() { HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET); } SpiTransaction(const SpiTransaction) delete; SpiTransaction operator(const SpiTransaction) delete; private: SPI_HandleTypeDef* hspi_; };错误处理策略enum class DriverError { None, Timeout, HardwareFault, InvalidParameter }; template typename T class Result { public: Result(T value) : value_(value), error_(DriverError::None) {} Result(DriverError error) : error_(error) {} bool isOk() const { return error_ DriverError::None; } T value() const { return value_; } DriverError error() const { return error_; } explicit operator bool() const { return isOk(); } private: T value_{}; DriverError error_; }; Resultuint32_t readSensorData() { if (checkHardwareFault()) { return DriverError::HardwareFault; } return 0xABCD1234; // 示例数据 }线程安全设计template typename T class Atomic { public: explicit Atomic(T value T{}) : value_(value) {} T load() const { CriticalSection cs; return value_; } void store(T value) { CriticalSection cs; value_ value; } T exchange(T value) { CriticalSection cs; T old value_; value_ value; return old; } private: T value_; };9. 嵌入式C驱动开发常见问题与解决方案9.1 虚函数表问题问题在无MMU的嵌入式系统中虚函数调用可能导致性能问题或内存浪费。解决方案限制虚函数使用仅在必要时使用使用编译时多态模板代替运行时多态手动实现vtable高级技巧// 手动vtable示例 struct UartVtable { void (*write)(void* self, const char* data, size_t size); size_t (*read)(void* self, char* buffer, size_t maxSize); }; class UartDriver { public: UartDriver(const UartVtable* vtable, void* impl) : vtable_(vtable), impl_(impl) {} void write(const char* data, size_t size) { vtable_-write(impl_, data, size); } size_t read(char* buffer, size_t maxSize) { return vtable_-read(impl_, buffer, maxSize); } private: const UartVtable* vtable_; void* impl_; };9.2 异常处理问题问题嵌入式系统通常禁用C异常需要替代方案。解决方案使用错误码返回使用Result 模式如前所示使用setjmp/longjmp谨慎使用class NoThrowAllocator { public: static void* allocate(size_t size) noexcept { void* p malloc(size); if (p nullptr) { // 记录错误但不会抛出异常 errorHandler(Error::OutOfMemory); } return p; } static void setErrorHandler(std::functionvoid(Error) handler) { errorHandler std::move(handler); } private: static std::functionvoid(Error) errorHandler; };10. 嵌入式C驱动开发进阶话题10.1 驱动与RTOS集成在RTOS环境中开发驱动需要考虑任务同步和资源共享class ThreadSafeSpiDriver { public: ThreadSafeSpiDriver(SPI_HandleTypeDef* hspi, osMutexId_t mutex) : hspi_(hspi), mutex_(mutex) {} void transfer(const uint8_t* txData, uint8_t* rxData, size_t size) { osMutexAcquire(mutex_, osWaitForever); HAL_SPI_TransmitReceive(hspi_, txData, rxData, size, HAL_MAX_DELAY); osMutexRelease(mutex_); } private: SPI_HandleTypeDef* hspi_; osMutexId_t mutex_; };10.2 低功耗驱动设计低功耗是嵌入式系统的重要考量class LowPowerTimer { public: void start(uint32_t timeoutMs) { // 配置硬件定时器唤醒 HAL_RTCEx_SetWakeUpTimer_IT(hrtc, timeoutMs, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 进入停止模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后恢复时钟配置 SystemClock_Config(); } static void handleInterrupt() { __HAL_RTC_WAKEUPTIMER_EXTI_CLEAR_FLAG(); } };10.3 驱动安全考虑安全关键系统需要特别注意template typename T, size_t Size class ProtectedBuffer { public: bool write(const T* data, size_t count) { if (count Size) return false; checksum_ computeChecksum(data, count); memcpy(buffer_, data, count * sizeof(T)); return true; } bool read(T* dest, size_t count) const { if (count Size) return false; if (computeChecksum(buffer_, count) ! checksum_) { return false; } memcpy(dest, buffer_, count * sizeof(T)); return true; } private: uint32_t computeChecksum(const T* data, size_t count) const { uint32_t sum 0; for (size_t i 0; i count; i) { sum (sum 5) sum reinterpret_castconst uint8_t*(data[i])[0]; } return sum; } T buffer_[Size]; uint32_t checksum_ 0; };

相关新闻

机房精细化运维:U 位资产管理如何解决数据中心资产管控痛点

机房精细化运维:U 位资产管理如何解决数据中心资产管控痛点

随着政企自建机房、商用 IDC 规模持续扩张,机柜密度不断提升,机房内服务器、交换机等 IT 设备数量持续增长。传统运维依靠人工记录机柜机位信息,普遍存在 U 位占用情况不清、设备移位无法及时发现、全机房盘点耗时漫长、空闲机位资源无法合理…

2026/10/10 15:39:58 阅读更多 →
学习主题:Linux 文件系统基本管理 + 硬盘分区管理

学习主题:Linux 文件系统基本管理 + 硬盘分区管理

Linux速成记 - 文件系统与硬盘分区学习主题:Linux 文件系统基本管理 硬盘分区管理一、复习和预习 昨天课堂内容回顾 源码安装应用自建仓库计划任务进程调度 今天课前练习 课前测试:回顾上节课学习的源码安装、YUM仓库配置等内容。二、Linux 文件系统基本…

2026/10/14 12:53:01 阅读更多 →
Ghidra逆向工程入门:从零搭建分析环境到实战拆解程序逻辑

Ghidra逆向工程入门:从零搭建分析环境到实战拆解程序逻辑

1. 项目概述:为什么选择Ghidra作为你的第一把“手术刀”?如果你对软件的内部运作机制充满好奇,想看看一个程序在二进制层面究竟是如何“思考”和“行动”的,那么逆向工程就是你进入这个神秘世界的钥匙。而Ghidra,作为美…

2026/10/5 14:45:00 阅读更多 →

最新新闻

Fontsource 字体解析:Amarante —— 基于 Art Nouveau 的中等对比度展示字体

Fontsource 字体解析:Amarante —— 基于 Art Nouveau 的中等对比度展示字体

前端CLI开发工具 【免费下载链接】fontsource Self-host Open Source fonts in neatly bundled NPM packages. 项目地址: https://gitcode.com/gh_mirrors/fo/fontsource 点击查看 免费下载 Amarante 是 Fontsource 注册表中收录的 Google 系字体之一,由…

2026/10/15 5:39:22 阅读更多 →
372.高通 EDL Sahara/Firehose 协议详解,安卓黑砖救砖 + 分区镜像校验实战源码

372.高通 EDL Sahara/Firehose 协议详解,安卓黑砖救砖 + 分区镜像校验实战源码

摘要 本文面向具备基础计算机操作能力的开发者与维修人员,系统性地阐述安卓设备刷机与维修的底层逻辑。文章从BootROM到Android系统的启动链路出发,深入解析Fastboot与EDL两种核心刷机协议的通信机制,结合真实变砖救援案例,提供完整的操作流程与可运行的Python自动化脚本。…

2026/10/15 5:39:22 阅读更多 →
直流偏磁下变压器励磁电流谐波分析:Simulink仿真与FFT指南

直流偏磁下变压器励磁电流谐波分析:Simulink仿真与FFT指南

简介:这份文档是重庆邮电大学自动化学院/工业互联网学院学生在“复杂电气工程系统设计”课程中完成的A等实验报告,主题聚焦直流偏磁下变压器励磁电流谐波分析。报告从地磁风暴与高压直流输电两大现实场景切入,系统阐述直流偏磁的产生机理、对…

2026/10/15 5:39:22 阅读更多 →
基于SSM的门面商铺租赁管理系统设计与实现

基于SSM的门面商铺租赁管理系统设计与实现

每年到这个时间点,总有准备毕设的同学来问:JavaWeb方向到底选什么题目,既能体现技术功底,又不至于把自己逼到绝路。如果你正在“基于SSM的门面商铺租赁管理系统”这个题目上犹豫,我直接说结论:这是一个非常…

2026/10/15 5:39:22 阅读更多 →
MutationObserver:高效监控DOM变化的现代API

MutationObserver:高效监控DOM变化的现代API

1. MutationObserver 基础解析MutationObserver 是现代浏览器提供的原生API,用于监测DOM树中的节点变化。与传统的Mutation Events相比,它具有显著的性能优势:异步触发机制避免频繁回调导致的性能问题批量处理DOM变更,减少不必要的…

2026/10/15 5:39:22 阅读更多 →
Multi-Agent系统Tracing实战:从线上事故到执行链记录体系

Multi-Agent系统Tracing实战:从线上事故到执行链记录体系

1. 从一次线上事故说起:为什么Multi-Agent系统必须做Tracing去年冬天,我们团队上线了一套基于多智能体协作的自动化内容处理系统,四个Agent分别负责信息抽取、逻辑校验、格式转换和最终输出。上线第三天,运营同学反馈说有一批文档…

2026/10/15 5:38:22 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →