news 2026/5/11 19:33:35

开源RISC-V软核NEORV32:从架构解析到FPGA实战开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
开源RISC-V软核NEORV32:从架构解析到FPGA实战开发指南

1. 项目概述:一个开源的RISC-V软核处理器

如果你正在寻找一个能放进FPGA里的、功能齐全且完全开源的RISC-V处理器核心,那么stnolting/neorv32这个项目绝对值得你花时间深入研究。它不是一个简单的玩具核,而是一个经过精心设计、文档详尽、并且在实际项目中得到验证的32位RISC-V微控制器系统。我第一次接触它,是因为一个需要高度定制化控制逻辑的FPGA项目,市面上通用的MCU要么引脚不够灵活,要么外设不符合需求,而自己从头写一个处理器又工程浩大。NEORV32的出现,完美地解决了这个痛点——它让你能以“软件定义硬件”的方式,在FPGA内部构建一个完全属于你的片上系统。

简单来说,NEORV32是一个采用VHDL语言编写的、可综合的RISC-V处理器IP核。它严格遵循RISC-V基金会发布的指令集架构规范,并且实现了包括机器模式(M)、用户模式(U)在内的完整特权级架构。更吸引人的是,它不仅仅是一个孤立的CPU核心,而是一个包含了处理器核心、片上存储器、丰富外设(如UART、SPI、GPIO、定时器)以及系统总线(如Wishbone)的完整微控制器平台。你可以把它理解为一个“可裁剪、可任意配置的软核版ARM Cortex-M”,但它是完全开源、免版税的,并且基于开放的RISC-V生态。

这个项目非常适合几类开发者:首先是FPGA开发者,你可以在自己的FPGA设计里快速嵌入一个处理器子系统,用于处理控制流、协议栈或复杂状态机;其次是嵌入式系统学习者,你可以通过它深入理解CPU、总线、外设是如何协同工作的,因为所有RTL代码都一览无余;最后是那些对供应链安全或技术自主有要求的团队,一个完全可控的、从RTL到工具链都开源的处理器核心,提供了极高的灵活性和安全性。

2. 核心架构与设计哲学解析

2.1 模块化与高度可配置性

NEORV32最突出的设计理念就是模块化高度可配置性。这并非简单的口号,而是贯穿于其整个代码库和构建系统的核心思想。作者将处理器系统的各个组成部分,如整数单元(CPU)、中断控制器(CLINT)、内存管理单元(PMP)、调试模块(DM)、以及各种外设,都设计成了独立的、可选的VHDL实体。

这种设计带来的直接好处是,你可以像搭积木一样构建你的专属处理器。例如,如果你的应用对性能要求不高,但对面积(即FPGA的LUT/FF资源消耗)极其敏感,你可以选择禁用乘法扩展(M扩展)、甚至只保留最基本的整数指令集(I扩展),并移除所有非必需的外设。反之,如果你需要一个功能强大的控制核心,你可以启用所有的标准扩展(M、C、Zicsr、Zicntr等),并挂载上SPI、I2C、PWM等丰富的外设。这种灵活性是通过一组综合时的generic参数来实现的,你在实例化NEORV32顶层实体时,通过设置这些参数就能完成定制。

注意:在配置时,务必仔细阅读官方数据手册中的“配置与实现”章节。有些配置选项之间存在依赖关系。例如,启用用户模式(U)必须同时启用物理内存保护(PMP)单元;启用调试模块(DM)会引入额外的逻辑和面积开销。盲目启用所有功能可能会导致你的设计无法在目标FPGA上适配。

2.2 总线架构与系统集成

NEORV32内部采用了两套主要的总线来连接处理器核心与系统其他部分,这体现了其清晰的分层设计思想。

  1. 处理器内部总线:核心与紧耦合的指令存储器(IMEM)和数据存储器(DMEM)之间采用专用的、低延迟的接口。这保证了CPU取指和访问关键数据的速度,是处理器高性能的基础。通常,这些存储器会直接映射到FPGA的块RAM(BRAM)上。

  2. Wishbone总线:这是NEORV32与外部世界(即片上外设和外部存储器)通信的标准化桥梁。NEORV32实现了一个Wishbone主机接口。Wishbone是一种轻量级、开源、被广泛采用的片上总线协议。选择Wishbone意味着极强的互操作性。你可以轻松地将其他开源的Wishbone外设IP(如图形控制器、以太网MAC等)连接到NEORV32上,也可以将NEORV32作为一个主设备,接入到你已有的、更复杂的Wishbone总线系统中。

这种“核心-内部总线-系统总线”的分层结构,使得NEORV32既能保证核心效率,又能方便地扩展系统功能。在实际集成时,你需要一个总线互联器(比如交叉开关或简单的解码器)来管理多个从设备(如UART、GPIO、定时器)的地址映射和仲裁。NEORV32的示例项目通常会包含一个这样的顶层互联逻辑。

2.3 特权级与安全特性

NEORV32完整实现了RISC-V特权架构,支持机器模式(M-mode)和用户模式(U-mode)。这对于构建健壮的、具备一定安全性的嵌入式系统至关重要。

  • 机器模式:这是最高特权模式,CPU复位后即运行于此模式。操作系统内核或裸机应用的核心部分在此模式下运行,可以访问所有CPU寄存器和系统控制寄存器(CSR),执行所有指令。
  • 用户模式:较低特权模式。应用程序通常运行在此模式下。在该模式下,对某些敏感CSR寄存器和指令的访问会被禁止,从而将应用程序与系统关键资源隔离开来。

实现模式切换的关键是物理内存保护单元。PMP允许机器模式下的软件(如操作系统)为特定的物理内存地址区域设置访问权限(读、写、执行)。当CPU在用户模式下尝试访问内存时,PMP单元会检查此次访问是否被允许,如果违反规则则触发异常。这样,即使某个用户程序发生错误或恶意行为,也无法破坏其他程序或内核的数据。

对于许多嵌入式应用来说,可能一开始觉得用不到用户模式。但我个人的经验是,如果你的系统中有多个相对独立的功能模块,或者未来有功能扩展的计划,尽早规划并启用用户模式+PMP是很有远见的做法。它为你未来的软件架构提供了硬件级别的隔离保障,相当于在软件世界筑起了一道防火墙。

3. 从零开始:开发环境搭建与项目构建

3.1 工具链准备:软件与硬件的桥梁

要为NEORV32编写程序,你需要一套完整的RISC-V GNU工具链。这包括编译器(riscv-none-elf-gcc)、汇编器、链接器、调试器等。这里我推荐使用xPack项目预编译好的工具链,它省去了自己编译的麻烦,且版本管理清晰。

# 以 macOS 或 Linux 为例,使用 curl 下载并解压 curl -L https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v12.2.0-1/xpack-riscv-none-elf-gcc-12.2.0-1-darwin-arm64.tar.gz -o toolchain.tar.gz tar -xzf toolchain.tar.gz # 将工具的bin目录添加到PATH环境变量 export PATH=$PATH:$(pwd)/xpack-riscv-none-elf-gcc-12.2.0-1/bin

验证安装:

riscv-none-elf-gcc --version

对于Windows用户,同样可以在xPack发布页找到对应的.zip包,解压后配置环境变量即可。务必确保工具链的targetriscv-none-elfriscv32-unknown-elf,这表示它是为嵌入式(无操作系统)环境编译的。

3.2 FPGA开发环境与综合流程

NEORV32主要使用VHDL编写,因此任何支持VHDL-2008标准的FPGA开发工具都能使用。最常用的包括:

  • Xilinx Vivado:用于Xilinx 7系列、UltraScale等器件。
  • Intel Quartus Prime:用于Intel(原Altera) Cyclone、Arria、Agilex等器件。
  • 开源工具链:对于Lattice的某些器件(如iCE40、ECP5),你可以使用Yosys(综合)+ nextpnr(布局布线)+ openFPGALoader(编程)这一套完全开源的工具链,这与NEORV32的开源精神非常契合。

以在Vivado中使用NEORV32为例,步骤并不复杂,但有几个关键点:

  1. 获取源码:直接从GitHub克隆项目,git clone https://github.com/stnolting/neorv32.git。项目结构清晰,rtl目录下是所有VHDL源文件,sim目录是仿真文件,sw目录是软件示例和库。

  2. 创建Vivado工程:新建一个RTL项目,目标器件选择你的FPGA型号(例如Artix-7系列的XC7A35T)。

  3. 添加源文件:不要一次性添加所有rtl下的文件。最好的做法是参考项目提供的setup脚本或示例项目(如rtl/top_templates中的模板)。通常,你需要添加:

    • 核心文件:rtl/core/*.vhd
    • 你计划使用的外设文件:rtl/system_integration/*.vhd中的对应模块
    • 一个顶层的包装文件(Wrapper):你可以直接修改rtl/top_templates/neorv32_top_axi4lite.vhdneorv32_top_stdlogic.vhd。这个文件定义了处理器系统的所有generic参数,是你进行定制的主要入口。
  4. 配置顶层参数:在顶层文件中,找到GENERIC映射部分。这里你需要决定:

    • CLOCK_FREQUENCY:你的板载时钟频率,这直接影响UART波特率等外设定时。
    • INT_BOOTLOADER_EN:是否启用内置的引导加载程序(Bootloader)。强烈建议在开发初期启用它。它允许你通过UART直接更新FPGA内存中的程序,无需重新综合和烧写整个FPGA比特流,极大提升调试效率。
    • MEM_INT_IMEM_EN/MEM_INT_DMEM_EN:是否使用内部的指令/数据存储器,以及它们的大小(通常设置为8KB或16KB)。
    • 各种外设的使能开关:如IO_GPIO_EN,IO_MTIME_EN,IO_UART0_EN等。
  5. 综合、实现与生成比特流:配置完成后,运行综合(Synthesis)、实现(Implementation)并生成比特流(Generate Bitstream)。在这个过程中,Vivado会报告资源使用情况(LUT、FF、BRAM)。这是你评估设计是否适合目标器件的关键步骤。

3.3 第一个“Hello World”程序的编译与加载

硬件设计好了,接下来是软件部分。NEORV32的sw目录下有丰富的示例。

  1. 进入示例目录cd neorv32/sw/example/hello_world

  2. 配置项目:编辑main.c,这就是你的程序。一个简单的Hello World程序已经写好,它通过UART输出字符串。

  3. 编译:项目使用Makefile管理。确保RISCV_PREFIX环境变量指向你的工具链路径,然后直接运行make

    export RISCV_PREFIX=/path/to/your/toolchain/bin/riscv-none-elf- make clean all

    编译成功后,会生成几个关键文件:

    • neorv32_exe.bin:原始的二进制执行文件。
    • neorv32_exe.hex:Intel HEX格式文件,便于某些加载器读取。
    • neorv32_exe.vhd:一个VHDL文件,其中包含了程序代码,以常量数组的形式定义。这是用于初始化片上ROM的关键文件
  4. 加载程序:有两种主要方式:

    • 方式一:通过Bootloader(推荐):如果你的设计启用了内置Bootloader,在生成FPGA比特流并烧写到板子后,处理器会首先运行Bootloader。Bootloader会等待主机通过UART发送新的程序。使用项目提供的sw/image_gen工具和Python上传脚本,可以方便地完成上传。
      python3 neorv32/sw/image_gen/neorv32_image_generator.py -i neorv32_exe.bin -o upload.hex python3 neorv32/sw/bootloader/neorv32_bootloader.py /dev/ttyUSB0 upload.hex # 串口设备名需修改
    • 方式二:直接编译进硬件:将生成的neorv32_exe.vhd文件作为设计源文件之一,替换掉原来的rtl/core/neorv32_imem.mem.vhd文件(或在其基础上修改)。然后重新综合生成比特流。这样程序就被“固化”到FPGA的配置中了。这种方式适用于最终产品。

当你看到串口终端上打印出“Hello World! This is NEORV32!”时,恭喜你,整个软硬件链路已经打通了。

4. 关键外设使用与驱动开发实战

4.1 通用输入输出(GPIO)的灵活控制

GPIO是最基础也最常用的外设。NEORV32的GPIO模块设计得非常直观。在软件层面,它通过内存映射的寄存器进行控制。

  1. 寄存器映射:在sw/lib/include/neorv32.h头文件中,你可以找到所有外设的寄存器定义。对于GPIO,关键寄存器有:

    • GPIO_OUTPUT:写这个寄存器可以设置输出引脚的电平。
    • GPIO_INPUT:读这个寄存器可以获取输入引脚的电平。
    • GPIO_DIR:方向控制寄存器。每一位对应一个引脚,1为输出,0为输入。
  2. 基础操作示例:假设我们想周期性地翻转连接在GPIO第0引脚上的LED。

    #include "neorv32.h" int main() { // 初始化UART,用于打印信息(可选) neorv32_uart0_setup(115200, 0); // 0表示不使用中断 // 检查GPIO单元是否在硬件中启用 if (neorv32_gpio_available() == 0) { neorv32_uart0_print("Error: GPIO not synthesized!\n"); return 1; } // 将GPIO第0引脚设置为输出模式 neorv32_gpio_port_set(0); // 先设置输出值(可选) neorv32_gpio_pin_set_dir(0, 1); // 引脚索引,方向(1=输出) // 主循环 while(1) { neorv32_gpio_pin_toggle(0); // 翻转第0引脚 neorv32_cpu_delay_ms(500); // 利用CPU的延时函数等待500ms } return 0; }

    实操心得neorv32_cpu_delay_ms函数是一个忙等待延时,它会占用CPU。在简单的闪烁LED任务中没问题,但在复杂的多任务系统中,应尽量避免使用忙等待,而是使用定时器中断来触发事件,以释放CPU资源。

4.2 通用异步收发器(UART)的双向通信

UART是调试和与外界通信的生命线。NEORV32的UART驱动库已经封装得很好。

  1. 初始化与发送

    // 初始化UART0,波特率115200,不使用中断(轮询模式) neorv32_uart0_setup(115200, 0); // 发送字符串 neorv32_uart0_print("System booted successfully.\n"); // 发送单个字符 neorv32_uart0_putc('A'); // 格式化打印(类似printf) int value = 42; neorv32_uart0_printf("The answer is %d.\n", value);
  2. 接收数据(轮询方式)

    char c; if (neorv32_uart0_char_received()) { // 检查是否有字符到达 c = neorv32_uart0_getc(); // 读取字符 neorv32_uart0_putc(c); // 回显 }
  3. 使用中断接收:对于需要及时响应串口数据的应用,中断模式更高效。

    // 初始化UART0,并使能接收中断 neorv32_uart0_setup(115200, 1 << 10); // 使能RX中断 // 在中断服务程序(ISR)中处理 void __attribute__((interrupt)) uart0_rx_isr(void) { char c = neorv32_uart0_getc(); // 将字符放入环形缓冲区,供主循环处理 buffer_put(c); // 清除中断挂起位(驱动库通常会处理) }

    重要提示:使用中断前,必须确保在系统初始化时全局中断已开启(neorv32_cpu_eint()),并且正确配置了中断向量表。NEORV32的中断处理机制遵循RISC-V标准,需要编写汇编或C语言包装的中断入口函数。

4.3 系统定时器(MTIME)与实时操作系统的基石

RISC-V标准定义了一个机器模式下的定时器(mtime/mtimecmp),NEORV32实现了它。这是实现延时、超时、周期性任务乃至运行实时操作系统(RTOS)的基础。

  1. 基本延时:我们已经用过neorv32_cpu_delay_ms,其内部就是基于mtime实现的。

  2. 定时器中断:这是更强大的用法。你可以设置一个未来的时间点,当mtime寄存器值达到你设置的mtimecmp值时,会触发机器模式定时器中断(MTI)。

    #include "neorv32.h" volatile uint32_t timer_ticks = 0; void __attribute__((interrupt)) timer_isr(void) { timer_ticks++; neorv32_mtime_set_timecmp(neorv32_mtime_get_time() + NEORV32_SYSINFO_CLK / 10); // 设置下一次中断在0.1秒后 } int main() { // ... 其他初始化 // 设置第一次定时器中断在1秒后触发 uint64_t now = neorv32_mtime_get_time(); neorv32_mtime_set_timecmp(now + NEORV32_SYSINFO_CLK); // CLK是每秒的时钟周期数 // 配置并启用定时器中断 neorv32_cpu_csr_write(CSR_MIE, 1 << CAUSE_MACHINE_TIMER); // 使能MTI中断 neorv32_cpu_eint(); // 全局中断使能 while(1) { if (timer_ticks >= 10) { // 每10次中断(即1秒)执行一次 neorv32_gpio_pin_toggle(LED_PIN); timer_ticks = 0; } // 主循环可以执行其他低优先级任务 idle_task(); } }

    经验之谈:基于mtime的定时器中断是构建任何时间敏感型应用的基石。许多针对RISC-V的RTOS(如FreeRTOS的RISC-V端口)都依赖此硬件定时器来实现任务调度。理解并掌握它,是迈向复杂嵌入式系统开发的关键一步。

5. 高级主题:性能优化、调试与自定义扩展

5.1 性能分析与优化策略

作为一个软核处理器,NEORV32的性能直接受限于FPGA的逻辑资源和工作频率。优化需要从硬件配置和软件编写两方面入手。

硬件配置优化:

  • 启用指令缓存:如果您的程序较大,且运行在外部慢速存储器上,启用指令缓存(ICACHE)能显著提升性能。在顶层generic中设置ICACHE_EN => true。缓存大小(ICACHE_NUM_BLOCKS,ICACHE_BLOCK_SIZE)需要根据程序的工作集大小和可用BRAM资源进行权衡。
  • 启用CPU扩展:确保启用了CPU_EXTENSION_RISCV_C(压缩指令扩展)。这能让代码密度提高约20%-30%,减少取指次数,间接提升性能。CPU_EXTENSION_RISCV_M(乘除法扩展)对于涉及大量计算的程序是必须的,否则乘除法将由软件库模拟,极其缓慢。
  • 调整总线宽度:Wishbone总线数据宽度默认为32位。如果连接了支持突发传输(Burst Transfer)的高速外设(如SDRAM控制器),可以尝试增加总线宽度(如64位)以提高吞吐量,但这会增加逻辑资源消耗。

软件优化:

  • 关键循环使用汇编:对于最内层、最耗时的循环(如图像处理、加密算法),可以考虑用RISC-V汇编重写。GCC编译器虽然强大,但手写汇编有时能更好地利用指令流水线和寄存器。
  • 数据对齐:确保频繁访问的数据结构(尤其是数组)在内存中按自然边界对齐(如4字节对齐)。未对齐的访问在某些架构上会导致性能损失或触发异常。
  • 使用编译器优化:合理使用GCC的优化选项,如-O2-Os(优化尺寸)。对于性能关键部分,可以尝试-O3,但需注意可能增加的代码体积。

5.2 调试技巧:从日志到硬件调试器

调试嵌入式系统是门艺术。NEORV32提供了多种调试手段。

  1. UART打印大法:最基础也是最强大的方法。在代码关键位置插入neorv32_uart0_printf打印变量状态、函数入口等信息。为了不干扰实时性,可以定义一个宏,在调试版本中启用打印,在发布版本中禁用。

    #ifdef DEBUG #define DBG_PRINT(...) neorv32_uart0_printf(__VA_ARGS__) #else #define DBG_PRINT(...) ((void)0) #endif
  2. 利用内置的调试模块:NEORV32可选配一个基于RISC-V官方调试规范的调试模块(DM)。启用后,你可以通过JTAG接口,使用GDB等调试器进行单步执行、设置断点、查看和修改寄存器/内存。这需要额外的硬件(如JTAG调试器)和软件配置(OpenOCD),但它是调试复杂问题的终极武器。

  3. 仿真(Simulation):在将设计部署到FPGA之前,使用仿真器(如GHDL + GTKWave,或Modelsim/Questa)进行行为级仿真。NEORV32项目自带了完善的测试平台(Testbench)。你可以编写激励文件,模拟外设输入,观察处理器内部信号和内存变化,在早期发现逻辑错误。

  4. 性能计数器:NEORV32的CPU核心实现了Zicntr扩展,提供了机器模式下的性能计数器(如执行指令数、时钟周期数)。通过读取这些计数器,你可以对代码进行性能剖析(Profiling),找到热点函数。

    uint64_t start_cycles = neorv32_cpu_get_cycle(); my_critical_function(); uint64_t end_cycles = neorv32_cpu_get_cycle(); neorv32_uart0_printf("Function took %llu cycles.\n", end_cycles - start_cycles);

5.3 自定义外设集成:扩展你的系统

NEORV32的魅力在于你可以轻松扩展它。假设你需要连接一个自定义的传感器接口模块。

  1. 设计Wishbone从设备:用VHDL/Verilog编写你的外设逻辑。它需要实现Wishbone从设备接口(通常是Wishbone B4规范)。关键部分包括:

    • 地址解码:根据总线地址,决定是否响应本次操作。
    • 寄存器文件:提供一组可读/写的控制与状态寄存器。
    • 数据路径:将总线数据与你的传感器控制逻辑相连。
  2. 集成到系统

    • 在顶层VHDL文件中,将你的自定义外设实例化。
    • 修改总线互联逻辑(例如在rtl/system_integration/neorv32_busswitch.vhd或你自己的顶层互联模块中),为新设备分配一个唯一的地址空间。
    • 更新系统的地址映射表(在软件头文件中定义),让软件知道如何访问你的新设备。
  3. 编写设备驱动:在C语言中,为你的外设定义寄存器地址,并编写初始化、读写数据的函数。

    // 假设你的外设基地址被分配在 0xFFFF0000 #define MY_PERIPH_BASE 0xFFFF0000 #define MY_PERIPH_CTRL_REG (*(volatile uint32_t*)(MY_PERIPH_BASE + 0x00)) #define MY_PERIPH_DATA_REG (*(volatile uint32_t*)(MY_PERIPH_BASE + 0x04)) void my_periph_init() { MY_PERIPH_CTRL_REG = 0x01; // 写入控制字,启动设备 } uint32_t my_periph_read_data() { return MY_PERIPH_DATA_REG; }

这个过程完美诠释了“软硬件协同设计”。你可以根据应用需求,量身定制硬件加速器,并通过标准的Wishbone总线与NEORV32核心无缝集成,从而在灵活性和效率之间取得最佳平衡。这种能力,是使用固定硬核MCU所无法比拟的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 19:28:42

AI+RPA:从脚本自动化到智能体驱动的生产力革命

1. 项目概述&#xff1a;当AI遇见RPA&#xff0c;一场生产力工具的范式革命 最近在GitHub上看到一个挺有意思的项目&#xff0c;叫 aivanelabs/ai-rpa 。光看这个名字&#xff0c;就让人忍不住想点进去看看。AI和RPA&#xff08;机器人流程自动化&#xff09;这两个词&#x…

作者头像 李华
网站建设 2026/5/11 19:27:51

FPGA新手避坑指南:用SPWM驱动电机时,你的死区时间加对了吗?

FPGA电机驱动实战&#xff1a;SPWM死区时间设计的核心要点与避坑策略 在数字电源和电机控制领域&#xff0c;FPGA因其并行处理能力和精确时序控制而备受青睐。许多工程师在成功实现SPWM信号生成后&#xff0c;往往忽略了驱动电路中最致命的一环——死区时间设置。我曾亲眼见证过…

作者头像 李华
网站建设 2026/5/11 19:27:39

HI3798MV200网络驱动调试与PHY配置实战

1. HI3798MV200网络驱动调试入门指南 第一次拿到HI3798MV200开发板时&#xff0c;我兴冲冲地插上网线准备调试&#xff0c;结果发现网络死活不通。这种场景相信很多嵌入式开发者都遇到过&#xff0c;今天我就把从零开始调试网络驱动的完整过程分享给大家。 HI3798MV200是海思…

作者头像 李华
网站建设 2026/5/11 19:26:41

统一团队开发环境:用DevContainer告别“在我机器上好的”

在软件测试的日常工作中&#xff0c;你是否经常遇到这样的场景&#xff1a;开发人员提交了代码&#xff0c;信誓旦旦地说“在我机器上跑得好好的”&#xff0c;可一到测试环境就状况百出——依赖缺失、端口冲突、系统库版本不一致&#xff0c;甚至整个服务都启动不起来。测试人…

作者头像 李华
网站建设 2026/5/11 19:25:53

混合信号电路设计:AGND与DGND接地策略的实战权衡与布局艺术

1. 混合信号电路接地的核心挑战 第一次设计混合信号电路时&#xff0c;我最纠结的就是AGND和DGND到底该怎么接。记得当时用ADC0804做温度采集&#xff0c;数字信号总是不稳定&#xff0c;折腾了两周才发现是接地方式有问题。混合信号电路之所以难搞&#xff0c;本质上是模拟和数…

作者头像 李华