当前位置: 首页 > news >正文

STM32简易示波器/逻辑分析仪设计指南

核心模块深度剖析:

1. 模拟前端信号调理电路 (关键!性能瓶颈往往在这里):

  • 架构示例 (单通道简化版):

Input BNC/J ---> [R_protect 1K] ---+---> [TVS Diodes to GND and VDD]
                                    |
                                    +---> [Relay or Analog Switch (e.g., ADG1419) for 1x/10x] ---+--> [1x Path: Direct] ---+
                                    |                                                           |
                                    +------------------------------------------------------------+
                                                                                                 |
                                                                                                 V
                                                                                     [Precision Resistor Divider Network]
                                                                                                 |
                                                                                                 V
                                                                                     [High-Speed OpAmp Buffer/Amplifier (e.g., ADA4807)] ---> [DC Offset Adjust (OpAmp Summing)] ---> [Anti-Aliasing LPF (RC)] ---> ADC_INx
                                                                                                 ^
                                                                                                 |
                                                                                      [DAC Output (for Offset Control)] <--- STM32 DAC

 

  • 量程切换 (1x/10x):

    • 继电器: 机械寿命有限,切换慢,但导通电阻小,寄生电容小,隔离好。适合低频、高精度。

    • 精密模拟开关 (ADG1419): 切换快 (ns级),寿命长,体积小。关键参数:

      • R_on (导通电阻): 需足够小 (几Ω到几十Ω),且平坦,避免引入非线性误差。计算在最低量程下的衰减误差。

      • C_on/C_off (导通/关断电容): 影响高频响应。C_on 会与输入电阻形成低通,C_off 会引入容性负载。

      • 电荷注入: 开关切换瞬间注入的电荷会引起电压毛刺。选择低电荷注入型号,布局时尽量靠近运放输入。

    • 电阻网络: 使用高精度 (0.1%或0.01%)、低温漂 (<10ppm/°C) 的金属膜电阻。计算分压比时考虑 R_on 的影响。并联小电容补偿高频衰减。

  • 直流偏置调整:

    • 目的: 将双极性信号 (如 ±5V) 或地参考信号偏移到 ADC 输入范围 (0-3.3V) 中间 (如 1.65V)。

    • 实现: 使用运放求和电路。一路输入是调理后的信号,另一路输入由 STM32 DAC 提供可调的偏置电压。公式: V_out = - ( (R_f / R_signal) * V_signal + (R_f / R_dac) * V_dac ) (反相求和)。调整 V_dac 即可移动信号基线。

  • 抗混叠滤波器 (AAF):

    • 必要性: 根据奈奎斯特采样定理,采样率 fs 必须 > 2 * f_max (信号最高频率分量)。AAF 在采样前滤除高于 fs/2 的频率分量,防止混叠失真。

    • 设计: 简单的 1阶或2阶 RC/Active LPF。截止频率 fc 通常设置为目标最大显示频率 f_disp_max 的 2-5 倍 (因为 Min-Max 显示算法需要更高采样率来保留细节),但必须 < fs/2。例如:目标显示 1MHz 信号,采样率 10MSPS,fc 可设在 2.5MHz - 5MHz。

    • 元件: 电阻需稳定,电容需 NP0/C0G 类型 (低损耗、低介电吸收、温漂小)。

2. ADC 配置与极限性能压榨 (以 STM32H743 为例,目标 3.6MSPS+):

  • 时钟配置:

    • ADCCLK 来源:通常选择 per_ck (由 PLL 提供)。H743 允许 ADCCLK 最高 50MHz (Vcore=1.3V)。

    • 在 RCC 中配置 PLL 输出 per_ck 为所需频率 (如 100MHz)。

    • 设置 ADC 预分频器 ADCx_CCR[CKMODE] 或 ADCx_CCR[PRESC] 使 ADCCLK <= 50MHz (如 per_ck=100MHz / 2 = 50MHz)。

  • ADC 模式配置 (CubeMX/寄存器):

    • ADCx_CFGR:

      • RES00 (12-bit resolution)

      • DMNGT11 (DMA Circular mode) 必须!

      • CONT1 (Continuous conversion mode) - 由定时器触发启动序列。

      • OVRMOD0 (Overrun overwrites) - 或 1 (产生中断),结合双缓冲处理。

      • EXTEN01 (Hardware trigger detection on the rising edge) 关键!

      • EXTSEL: 选择触发源对应的定时器 TRGO (如 TIM1_TRGO).

    • ADCx_SMPR1/2采样时间 (t_samp) 是速度关键!最短采样时间由信号源阻抗 (R_s) 和 ADC 输入电容 (C_adc, ~pF) 决定。t_samp >= 5 * R_s * C_adc (粗略)。对于前端缓冲后的低阻抗源 (R_s < 100Ω),可使用最短采样时间 (e.g., SMP=000 = 1.5 cycles @ ADCCLK)。计算转换时间 t_conv = t_samp + t_conv12bit (通常 8.5 cycles @12-bit)t_conv 决定 理论最大采样率 f_s_max = ADCCLK / t_conv。例如:ADCCLK=50MHzt_samp=1.5cyct_conv12bit=8.5cyct_conv=10cycf_s_max=5MSPS (但 STM32H743 ADC1 在 12-bit 下标称 3.6MSPS,需实测)。

  • 多通道扫描:

    • ADCx_SQR1L[3:0] 设置序列长度 (通道数)。

    • ADCx_SQR1/2/3/4SQx[4:0] 设置序列中第 x 个转换的通道号。

    • 采样率代价: 总采样率 f_s_total = f_s_max / N_channels。例如单通道可达 3.6MSPS,双通道扫描则每通道最大约 1.8MSPS。

  • 校准:

    • 上电后执行 HAL_ADCEx_Calibration_Start() (或寄存器操作 ADCAL=1)。校准偏移和线性度误差,存储在内部。必须做!

3. 定时器 (TIM) 配置 - 采样时钟源:

  • 目的: 产生精确的、可调的 PWM 或更新事件 (UEV) 来触发 ADC 采样。

  • 配置 (以 TIM1 高级定时器为例):

    • 时钟源: 内部时钟 (CK_INT),通常来自高速 PLL (如 400MHz)。

    • 分频器: PSC (预分频器寄存器)。Timer_CLK = CK_INT / (PSC + 1)

    • 计数器: ARR (自动重载寄存器)。计数器从 0 计数到 ARR

    • 触发输出 (TRGO):

      • 模式 (TIMx_CR2[MMS]): 选择 010 (更新事件 UEV 作为 TRGO) 或 011 (OC1REF 作为 TRGO)。

      • 如果选择 PWM 模式 (MMS=011):

        • TIMx_CCMR1[OC1M]110 (PWM 模式 1) 或 111 (PWM 模式 2)

        • TIMx_CCR1: 设置 PWM 占空比 (通常设成 ARR/2 产生方波)。触发发生在 OC1REF 上升沿或下降沿 (取决于 PWM 模式)。

    • 更新频率 (采样率 fs):

      • 更新事件频率 f_update = Timer_CLK / (ARR + 1)

      • 当使用 UEV 触发时: fs = f_update

      • 当使用 PWM 触发时: fs = f_update (触发频率等于更新频率,与占空比无关)

    • 动态调整采样率: 在运行时修改 PSC 或 ARR (通常通过用户旋转编码器事件触发)。注意: 修改 ARR 时,为避免计数不连续,可使用 TIMx_EGR(UG) 位产生一次更新事件,或使用预加载寄存器 (TIMx_CR1[ARPE]=1, 修改 ARR 后在下一次更新事件生效)。

4. DMA 双缓冲模式实现 (核心数据搬运):

  • 配置 (CubeMX/寄存器):

    • 外设地址: ADC1->DR (或 ADCx_COMMON->CDR for dual ADC)

    • 内存地址: 指向两个缓冲区的指针 adc_bufferA[] 和 adc_bufferB[] (类型 uint16_t)。大小: BUFFER_SIZE (每个缓冲区能容纳的样本数)。

    • 数据宽度: 外设:半字 (16-bit, 对应 DR),内存:半字。

    • 方向: 外设到内存。

    • 模式: 循环模式 (CIRC=1)。

    • 内存增量: 开启 (MINC=1)。

    • 外设增量: 关闭 (PINC=0)。

    • 双缓冲模式: 开启 (DBM=1)。

    • 传输长度: NDTR = 2 * BUFFER_SIZE (总长度 = BufferA + BufferB)。

    • 内存地址 0 (M0AR): &adc_bufferA[0]

    • 内存地址 1 (M1AR): &adc_bufferB[0]

    • 当前目标内存 (CT): 由 DMA 自动管理。CT=0 表示当前正在填充 M0AR (BufferA),CT=1 表示正在填充 M1AR (BufferB)。

    • 中断: 开启传输完成中断 (TCIE) 和半传输完成中断 (HTIE)。优先级: 设置为高优先级

中断服务程序 (ISR) 伪代码:

void DMA2_Stream0_IRQHandler(void) { // 假设 DMA2 Stream0 用于 ADC1if (__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_HTIF0)) { // Half-Transfer (BufferA full)__HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_HTIF0);g_adc_buf_ready = BUFFER_A_READY; // 设置全局标志通知主循环处理 BufferA}if (__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TCIF0)) { // Transfer-Complete (BufferB full)__HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TCIF0);g_adc_buf_ready = BUFFER_B_READY; // 设置全局标志通知主循环处理 BufferB}
}

主循环处理伪代码:

while (1) {if (g_adc_buf_ready != BUFFER_NONE) {uint16_t *current_buf;if (g_adc_buf_ready == BUFFER_A_READY) {current_buf = adc_bufferA;} else { // BUFFER_B_READYcurrent_buf = adc_bufferB;}g_adc_buf_ready = BUFFER_NONE; // 快速清除标志// --- 这里是数据处理核心区 ---// 1. 触发检测 (SearchTrigger(current_buf, BUFFER_SIZE))// 2. 找到触发点后,确定要显示的数据段 (考虑预触发点)// 3. 对显示段的数据应用波形压缩算法 (MinMaxCompress())// 4. 将压缩后的点数据转换为屏幕坐标// 5. 更新LCD波形显示 (DrawWaveform())// -------------------------}// ... 处理UI、菜单等其他任务 (非阻塞)
}

关键点:

  • ISR 必须极其精简!只设置标志,不做复杂计算。

  • 主循环中的数据处理部分 (// --- 这里是数据处理核心区 ---) 必须在下一个 DMA 中断到来前完成 (BUFFER_SIZE / fs 时间内)。否则会发生缓冲区覆盖,数据丢失。这是实时性的核心挑战!需要仔细优化算法。

软件触发实现细节 (边沿触发为例):

typedef enum { TRIGGER_RISING, TRIGGER_FALLING, TRIGGER_HIGH, TRIGGER_LOW } TriggerType;
typedef struct {uint8_t enabled;TriggerType type;uint8_t channel; // ADC channel index (0-based in buffer)uint16_t level;  // ADC raw value corresponding to trigger voltageint32_t position; // Found trigger position in buffer (-1 if not found)
} TriggerConfig;
TriggerConfig g_trigger;

触发搜索函数伪代码 (SearchTrigger):

int32_t SearchTrigger(uint16_t *buffer, uint32_t size) {if (!g_trigger.enabled) return -1; // Trigger disabled, always "trigger" at start or use free-runuint16_t prev_sample, curr_sample;uint32_t start_idx = 0; // Could start from a safe offsetfor (uint32_t i = start_idx + 1; i < size; i++) {prev_sample = buffer[i - 1]; // Previous sample for chosen channelcurr_sample = buffer[i];     // Current sample for chosen channelswitch (g_trigger.type) {case TRIGGER_RISING:if (prev_sample < g_trigger.level && curr_sample >= g_trigger.level) {g_trigger.position = i; // Trigger found at index ireturn i;}break;case TRIGGER_FALLING:if (prev_sample > g_trigger.level && curr_sample <= g_trigger.level) {g_trigger.position = i;return i;}break;case TRIGGER_HIGH: // Simpler, less commonif (curr_sample >= g_trigger.level) {g_trigger.position = i;return i;}break;case TRIGGER_LOW:if (curr_sample <= g_trigger.level) {g_trigger.position = i;return i;}break;}}g_trigger.position = -1; // Trigger not found in this bufferreturn -1;
}

显示数据段确定 (假设屏幕宽度 DISP_WIDTH 像素):

  • 目标: 在双缓冲区的当前处理块中找到 DISP_WIDTH 个点,以触发点为中心 (或按预触发比例偏移)。

  • 预触发: 假设我们希望在触发点前显示 PRE_TRIGGER_POINTS 个点 (如占总显示点数的 20%)。

int32_t trigger_pos = g_trigger.position; // Result from SearchTrigger
int32_t start_index;if (trigger_pos >= 0) { // Trigger foundstart_index = trigger_pos - PRE_TRIGGER_POINTS;if (start_index < 0) { // Not enough pre-trigger data? Pad with what we have.start_index = 0;}
} else { // No trigger found (or disabled). Use tail of buffer (free-run mode)start_index = size - DISP_WIDTH; // Show the latest pointsif (start_index < 0) start_index = 0;
}// Ensure we have DISP_WIDTH points available from start_index
uint32_t points_to_display = MIN(DISP_WIDTH, size - start_index);
  • 双缓冲区与预触发: 双缓冲区 (BUFFER_SIZE) 必须设置得足够大 (> DISP_WIDTH),才能存储触发点之前的数据。BUFFER_SIZE 决定了最大可捕获的预触发时间 (PRE_TRIGGER_TIME = BUFFER_SIZE / fs)。

6. Min-Max 波形压缩算法 (高效显示的核心):

  • 目的: 将 points_to_display 个原始采样点 (可能上千) 压缩到 DISP_WIDTH 个像素列上 (320列)。

  • 算法伪代码 (MinMaxCompress):

void MinMaxCompress(uint16_t *input, uint32_t input_len, uint16_t *min_buf, uint16_t *max_buf, uint32_t output_len) {uint32_t points_per_pixel = input_len / output_len; // May not be integeruint32_t remainder = input_len % output_len;uint32_t idx_in = 0;for (uint32_t pixel = 0; pixel < output_len; pixel++) {uint16_t pixel_min = 0xFFFF; // Initialize to max ADC valueuint16_t pixel_max = 0x0000; // Initialize to min ADC valueuint32_t points_this_pixel = points_per_pixel;// Distribute remainder to avoid cumulative errorif (remainder > 0) {points_this_pixel++;remainder--;}// Find min and max within this group of pointsfor (uint32_t p = 0; p < points_this_pixel; p++) {uint16_t val = input[idx_in++];if (val < pixel_min) pixel_min = val;if (val > pixel_max) pixel_max = val;}// Store results for this pixel columnmin_buf[pixel] = pixel_min;max_buf[pixel] = pixel_max;}
}
  • 绘制: 对于屏幕上的每一列 x (0 到 DISP_WIDTH-1):

    • 计算该列对应的最小点 y_min 和最大点 y_max (需将 ADC 值转换为屏幕 Y 坐标)。

    • 在 (x, y_min) 和 (x, y_max) 之间画一条垂直线。这是保留峰值信息最有效的方式。

  • 优化:

    • 使用查表法 (LUT) 将 ADC 值 (uint16_t) 直接转换为屏幕 Y 坐标 (uint8_t),避免浮点运算 y = (ADC_val - v_offset) * v_gain

    • 内层循环 (for (uint32_t p = ...) 是热点,用指针遍历,确保编译器优化良好。

7. 数字通道采集 (逻辑分析仪):

  • 原理: 使用另一个定时器 (TIMx) 触发,在固定时间间隔读取一组 GPIO 的状态。

  • 配置:

    • GPIO: 配置所需数量的 GPIO 为输入 (带上拉/下拉或浮空,根据需求)。

    • 定时器: 配置一个定时器 (TIMx) 产生更新事件 (UEV) 或 PWM (频率 = 数字采样率 f_digital)。f_digital 通常远高于模拟采样率 (f_analog),但受限于 GPIO 读取和存储速度。

    • DMA:

      • 外设地址: GPIOx->IDR (输入数据寄存器)。

      • 内存地址: uint16_t digi_bufferA[]uint16_t digi_bufferB[] (双缓冲)。

      • 数据宽度: 字 (32-bit) 或半字 (16-bit),取决于 IDR 宽度和需要的通道数。

      • 配置: 类似 ADC DMA (循环、双缓冲、定时器触发传输 TIMx_TRGO -> DMA_REQ)。触发源选择定时器更新事件。

  • 数据处理:

    • 每个 uint16_t/uint32_t 样本代表所有数字通道在采样时刻的电平 (bit0=ch0, bit1=ch1, ...)。

    • 在显示时,对每个通道,遍历显示时间窗口内的样本,检查对应 bit 是 1 还是 0。

    • 在对应像素列 x 上,如果 bit 为 1,从 Y_high 到 Y_high - height 画线;如果为 0,从 Y_low 到 Y_low + height 画线 (形成方波)。不同通道用不同颜色/高度错开显示。

8. 性能优化锦囊 (生死攸关):

  • 编译器优化: -O2 或 -O3-flto (链接时优化)。检查生成的汇编。

  • 数据局部性: 确保处理的数据在 Cache 中。使用 __attribute__((section(".ram_d2"))) 或 MPU 配置将 DMA 缓冲区和显示缓冲区放在最快的 RAM (如 DTCM on H7)。

  • 指令选择:

    • 整数运算: 优先使用 int32_t/uint32_t。避免浮点。

    • 位操作: 用位掩码和移位代替乘除2的幂。

    • 查表 (LUT): 对于重复计算 (ADC->Voltage->Y, Sin/Cos for FFT)。

    • 内联函数: 对关键小函数使用 __STATIC_INLINE

    • 汇编: 对绝对热点 (如 Min-Max 内层循环) 考虑手写汇编或 CMSIS-DSP 函数。

  • 外设加速:

    • DMA2D (图形加速器): 用于快速填充网格背景、绘制垂直线段、复制波形图像块。大幅提升 LCD 刷新率。

    • FPU (如果可用): 如果必须做浮点 (如 FFT),确保开启 FPU,用单精度 (float),向量化运算。

    • CRC: 校验数据传输 (可选)。

  • LCD 优化:

    • 局部刷新: 只更新波形区域,而非全屏。使用 DMA2D 区域填充/复制。

    • 直接写 GRAM: 使用 FSMC/FMC 的存储器映射模式直接操作 LCD GRAM 地址,比 SPI 命令快几个数量级。

    • 优化画点/线函数: 避免函数调用开销,直接操作内存或使用 DMA2D

  • 调试技巧:

    • GPIO 翻转: 在 ISR 入口/出口、处理开始/结束处翻转 GPIO,用示波器测量执行时间。

    • DWT 计数器: 使用 DWT->CYCCNT 进行 CPU 周期级精度的代码段计时。

    • 串口输出统计: 输出每个缓冲区处理耗时、最大耗时、触发成功率等。

9. 模拟前端设计计算示例 (10x衰减档位):

  • 目标: 输入 ±10V,输出到 ADC 范围 0-3.3V。

  • 衰减网络: R1 (输入电阻), R2 (对地电阻)。衰减比 Att = R2 / (R1 + R2) = 1/11 (10x probe is 1/10, but scope input usually 1M // ~20pF, so effective ratio is ~1/10.1 or similar. We use 1/11 for calculation simplicity).

  • 计算: 输入 +10V -> 输出 10V * (1/11) ≈ 0.909V。输入 -10V -> 输出 -10V * (1/11) ≈ -0.909V

  • 电平移位: 需要将 -0.909V ~ +0.909V 移位到 0V ~ 3.3V。中心点偏移 Offset = (0.909V - (-0.909V)) / 2 + (-0.909V) = 0.909V - 0.909V = 0V? 不对。

    • 当前范围:V_min_in = -0.909VV_max_in = +0.909V。中心点是 0V

    • 目标范围:V_min_out = 0VV_max_out = 3.3V。中心点是 1.65V

    • 需要增益 G 和偏移 V_os 满足:
      V_out = G * V_in + V_os
      @ V_in = -0.909V, V_out = 0V: 0 = G * (-0.909) + V_os
      @ V_in = +0.909V, V_out = 3.3V: 3.3 = G * (0.909) + V_os
      解方程: (2) - (1): 3.3 = G * 1.818 => G = 3.3 / 1.818 ≈ 1.815
      代入 (1): 0 = 1.815 * (-0.909) + V_os => V_os = 1.815 * 0.909 ≈ 1.65V

    • 结论: 需要一个增益 G ≈ 1.815,偏移 V_os = 1.65V 的同相求和放大器

  • 运放选择: 信号带宽要求。假设目标示波器带宽 BW_target = 5MHz。运放所需增益带宽积 GBW >= G * BW_target * Gain_Margin (e.g., 5) ≈ 1.815 * 5MHz * 5 ≈ 45.375MHz。选择 GBW > 50MHz 的运放 (如 ADA4807: 80MHz GBW)。

10. 硬件触发 (进阶):

  • 原理: 利用 STM32 内部的模拟比较器 (COMP) 或定时器输入捕获直接产生硬件信号停止 DMA 或标记位置,极大减少软件触发延迟。

  • 模拟比较器触发 (示例):

    1. 配置 COMPx (如 COMP1):

      • 反相端 (INM):连接 ADC 输入引脚 (经过调理的信号)。

      • 同相端 (INP):连接 DAC 输出 (设置触发电平)。

      • 输出极性:根据边沿选择。

      • 使能窗口模式 (如果需要)。

    2. 配置 COMPx 输出路由到定时器 (TIMx) 的刹车输入 (BKIN) 或作为 ADC 的触发源。

    3. 配置定时器 (TIMx) 工作在 One-Pulse 模式 或使用 COMP 输出作为门控。

    4. 当比较器输出跳变时,硬件立即响应:

      • 停止 ADC 转换 (通过定时器刹车)。

      • 或触发一个 DMA 请求将当前地址/状态保存到特定寄存器。

      • 或产生一个精确的中断。

    5. 软件在中断中读取保存的状态,精确知道触发发生的时刻 (在 DMA 缓冲区中的位置)。

  • 优点: 延迟极低 (ns 到 us 级)。

  • 缺点: 配置复杂,资源有限 (COMP 数量少),灵活性不如软件触发 (难以实现复杂条件)。

总结与行动路线:

  1. 选型定板: 确定 STM32 (F4/H7), LCD, 决定模拟通道数、数字通道数、目标带宽/采样率。设计或购买前端调理板。

  2. 搭建基础工程 (CubeMX):

    • 配置时钟树 (PLL -> High Speed Clocks)。

    • 配置定时器 (TIM_ADC, TIM_DIGITAL) 产生 TRGO。

    • 配置 ADC (规则组,定时器触发, DMA 双缓冲)。

    • 配置 GPIO for Digital Inputs (if used) and TIM_DIGITAL DMA.

    • 配置 USART/USB for debug/output.

    • 配置 FSMC/FMC/SPI for LCD.

    • 配置 NVIC (DMA, TIM interrupts high priority).

  3. 实现数据流:

    • 验证 ADC DMA 双缓冲填充正常 (用 debugger 看 buffer 或串口打印)。

    • 实现基本点绘制/滚动显示到 LCD。

  4. 实现触发:

    • 添加触发条件设置 (菜单/UI)。

    • 实现 SearchTrigger 函数。

    • 实现基于触发点的数据显示定位。

  5. 优化显示:

    • 实现 Min-Max 压缩算法。

    • 优化 LCD 绘图 (局部刷新, DMA2D)。

  6. 添加数字通道: 配置并实现数字采集和逻辑波形显示。

  7. 完善 UI & 功能: 菜单系统,量程切换 (控制继电器/DAC),测量功能 (Vpp, Freq)。

  8. (可选) 高级功能: USB 传输,SD 卡存储,硬件触发,FFT。

相关文章:

  • 02-性能方案设计
  • C++算法训练营 Day13二叉树专题(1)
  • 力扣-20.有效的括号
  • STM32标准库-ADC数模转换器
  • 基于ffmpeg+sdl的audio player
  • 模型重展UV后绘制纹理
  • [Java 基础]String 类
  • Java NIO详解:新手完全指南
  • 【技巧】dify前端源代码修改第一弹-增加tab页
  • python打卡day49@浙大疏锦行
  • 逻辑回归暴力训练预测金融欺诈
  • 电路图识图基础知识-远程/本地启停电动机(二十一)
  • 记录一篇HTTPS的文章
  • 如何让hustoj支持Java判题
  • 开放词汇检测分割YOLOE从pytorch到caffe
  • Spring状态机
  • Docker简述
  • React Hooks 的原理、常用函数及用途详解
  • Python打卡训练营学习记录Day49
  • 【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
  • 百度提交网站收录/百度知道问答首页
  • 重庆网站建设搜外/黄页网站推广app咋做广告
  • wordpress发布文章出现404/搜索引擎优化seo信息
  • 墨子学院seo/南宁百度推广排名优化
  • 开通微信公众号要钱吗/鸡西seo
  • 哪个网站可有做投票搭建/百度seo是什么