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

【STM32单片机】#9 DMA直接存储器存取

主要参考学习资料:

B站@江协科技

STM32入门教程-2023版 细致讲解 中文字幕

开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb

单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协

实验:

  • DMA数据转运
  • DMA+AD多通道

新函数:

  • DMA库函数

目录

  • DMA简介
  • 存储器映像
  • DMA框图
  • DMA基本结构
  • DMA请求
  • 数据宽度与对齐
  • 函数详解
    • DMA_DeInit函数
    • DMA_Init函数
      • DMA_InitTypeDef结构体
    • DMA_StructInit函数
    • DMA_Cmd函数
    • DMA_ITConfig函数
    • DMA_SetCurrDataCounter函数
    • DMA_GetCurrDataCounter函数
    • 中断标志位函数
  • 实验18 DMA数据转运
    • 任务分析
    • 接线图
    • DMA驱动
    • 主程序
  • 实验19 DMA+AD多通道
    • 任务分析
    • 接线图
    • 单次扫描-单次转运
      • ADC驱动
      • 主程序
    • 连续扫描-循环转运
      • ADC驱动
      • 主程序

DMA简介

  • DMA(Direct Memory Access)直接存储器存取
  • DMA可以提供外设和存储器或存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
  • STM32的DMA拥有12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)
  • 每个通道都支持软件触发和特定的硬件触发。存储器和存储器之间通常使用软件触发一次性全部转运,而外设到存储器的转运通常由硬件触发源触发以在正确的时机转运。
  • STM32F103C8T6资源:DMA1(7个通道)

存储器映像

存储器映像包含了STM32的所有存储器及其地址:

其中ROM是非易失性、掉电不丢失的只读存储器,RAM是易失性、掉电丢失的随机存储器。

从起始地址开始,存储器内部的每个字节都会分配一个独一无二的地址,终止地址取决于存储器的容量。外设寄存器中的每个外设又有自己的起始地址。

DMA框图

左上角为Cortex-M3内核,包含CPU和内核外设,其右侧设备均可看作存储器。总线矩阵可以高效有条理地访问存储器,左端是拥有存储器访问权的主动单元,右端是被主动单元读写的被动单元。DMA1和DMA2各有一条DMA总线,另一条DMA总线为以太网外设私有的DMA。DMA1和DMA2的各个通道可分别设置它们转运数据的源地址和目的地址,各自独立工作,但每个DMA的所有通道只能分时复用一条DMA总线,如果产生冲突,则由仲裁器根据通道的优先级决定先后顺序。总线矩阵也有一个仲裁器,如果DMA和CPU要访问同一个目标,则DMA会暂停CPU访问以防冲突,但总线仲裁器仍保证CPU得到一半的总线带宽以正常工作。AHB从设备是DMA自身的寄存器,作为被动单元由CPU配置。DMA请求是DMA的硬件触发源。

DMA基本结构

在数据转运中,转运的方向由参数控制,但由于Flash只读,因此无法将数据转运到Flash。外设站点和存储器站点(此为STM32官方称呼,实际上站点无外设和存储器之分)各有3个参数,起始地址决定数据从哪来、到哪去;数据宽度指定一次转运要按多大的数据宽度来进行,可选字节(8位)、半字(16位)、字(32位);地址是否自增指定一次转运完成后,下一次转运是否要把地址移动到下一个位置,以防数据覆盖。

传输计数器指定总共需要转运几次,是一个自减计数器,每转运一次计数器减一,减到零后不再进行数据转运,自增地址也会恢复为起始地址以便开始新一轮转运。自动重装器指定传输计数器减到零后是否要自动恢复到最初的值,即转运模式是单次还是循环。

触发决定DMA在什么时机转运,触发源由M2M决定,1为软件触发,0为硬件触发。软件触发会连续不断地触发转运直至传输计数器清零,因此无法与循环模式同时使用,适用于软件启动、无需时机、需尽快完成的存储器到存储器的转运。而外设到寄存器的转运需要一定的时机,通常使用硬件触发。

开关控制使能后则DMA可以转运。若为单次转运,则传输计数器清零后需关闭DMA,重新写入传输计数器的值再开启。

DMA请求

每个通道的硬件触发源不同,硬件触发必须使用相应的通道,而软件触发可以任意选择。每个通道选择哪个触发源由对应的外设是否开启了DMA输出决定。

数据宽度与对齐

该表列举了STM32在转运两端不同数据宽度配置下的处理方法。简而言之,目标宽度冗余则高位补零,目标宽度不足则舍弃高位。

函数详解

DMA_DeInit函数

简介:恢复缺省配置。

参数:DMA通道

DMA1_Channel1, ..., DMA1_Channel7
DMA2_Channel1, ..., DMA2_Channel5

DMA_Init函数

简介:初始化DMA。

参数一:DMA通道

参数二:DMA_InitTypeDef结构体指针

DMA_InitTypeDef结构体

成员DMA_PeripheralBaseAddr:外设站点起始地址

成员DMA_PeripheralDataSize:外设站点数据宽度

DMA_PeripheralDataSize_Byte(字节)
DMA_PeripheralDataSize_HalfWord(半字)
DMA_PeripheralDataSize_Word(字)

成员DMA_PeripheralInc:外设站点地址是否自增

DMA_PeripheralInc_Enable(自增)
DMA_PeripheralInc_Disable(不自增)

成员DMA_MemoryBaseAddr:存储器站点起始地址

成员DMA_MemoryDataSize:存储器站点数据宽度

DMA_MemoryDataSize_Byte(字节)
DMA_MemoryDataSize_HalfWord(半字)
DMA_MemoryDataSize_Word(字)

成员DMA_MemoryInc:存储器站点地址是否自增

DMA_MemoryInc_Enable(自增)
DMA_MemoryInc_Disable(不自增)

成员DMA_DIR:传输方向

DMA_DIR_PeripheralDST(存储器站点→外设站点)
DMA_DIR_PeripheralSRC(外设站点→存储器站点)

成员DMA_BufferSize:传输计数器值

成员DMA_Mode:是否自动重装

DMA_Mode_Circular(循环模式)
DMA_Mode_Normal(单次模式)

成员DMA_M2M:触发方式

DMA_M2M_Enable(软件触发)
DMA_M2M_Disable(硬件触发)

成员DMA_Priority:优先级

DMA_Priority_VeryHigh
DMA_Priority_High
DMA_Priority_Medium
DMA_Priority_Low

DMA_StructInit函数

简介:初始化DMA_InitTypeDef结构体。

参数:DMA_InitTypeDef结构体指针

DMA_Cmd函数

简介:DMA使能。

参数一:DMA通道

参数二:使能/失能

DMA_ITConfig函数

简介:DMA中断输出使能。

参数一:DMA通道

参数二:DMA中断源

DMA_IT_TC(转运完成)
DMA_IT_HT(转运过半)
DMA_IT_TE(转运错误)

DMA_SetCurrDataCounter函数

简介:设置当前传输计数器。

参数一:DMA通道

参数二:计数值

DMA_GetCurrDataCounter函数

简介:获取当前传输计数器值。

参数一:DMA通道

中断标志位函数

DMA_GetFlagStatus函数

DMA_ClearFlag函数

参数:DMA标志位

x为DMA名称,y为DMA通道
DMAx_FLAG_GLy(全局)
DMAx_FLAG_TCy(转运完成)
DMAx_FLAG_HTy(转运过半)
DMAx_FLAG_TEy(转运错误)

DMA_GetITStatus函数

DMA_ClearITPendingBit函数

参数:DMA中断源

实验18 DMA数据转运

任务分析

该实验我们将SRAM中的数组DataA转运到另一个数组DataB中。外设地址和存储器地址分别为DataA和DataB的首地址;数组类型为uint8_t,因此数据宽度为8位字节;每次转运完数组的一个元素后,两个站点的地址都应该自增移动到下一个元素的位置继续转运;方向为外设站点到存储器站点;传输计数器初值为数组元素个数,不需要自动重装;存储器到存储器的转运使用软件触发。

接线图

DMA驱动

该驱动存放在System文件夹和组中。

MyDMA.h

#ifndef __MYDMA_H
#define __MYDMA_Hvoid MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);#endif

MyDMA.c

#include "stm32f10x.h"//存储传输计数器初值的全局变量
uint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{MyDMA_Size = Size;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);DMA_InitTypeDef DMA_InitStructure;//外设站点//数组地址不固定,因此通过传入参数的数组名获取地址DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//数据宽度8位字节DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//地址自增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//存储器站点DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输计数器初值DMA_InitStructure.DMA_BufferSize = Size;//方向由外设到存储器DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//软件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//单次转运DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//优先级随意DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化不转运,等待手动调用再转运DMA_Cmd(DMA1_Channel1, DISABLE);
}//再次给传输计数器赋值启动DMA转运
void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);DMA_Cmd(DMA1_Channel1, ENABLE);//等待转运完成while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));//手动清除标志位DMA_ClearFlag(DMA1_FLAG_TC1);
}

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};int main(void)
{OLED_Init();MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);OLED_ShowString(1, 1, "DataA");OLED_ShowString(3, 1, "DataB");//显示数组地址OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);while(1){//DataA自增DataA[0] ++;DataA[1] ++;DataA[2] ++;DataA[3] ++;OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);//将DataA转运到DataBMyDMA_Transfer();OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);}
}

实验19 DMA+AD多通道

任务分析

左侧为ADC扫描模式执行流程,7个通道依次进行AD转换,转换结果都存入ADC_DR数据寄存器。我们需要在每个单独的通道转换完成之后进行一次DMA数据转运,并且目的地址自增,以防数据覆盖。DMA配置中,外设地址为ADC_DR寄存器地址,存储器地址是在SRAM中定义数组ADValue的地址;数据宽度根据uint16_t数据类型,选择半字传输;外设地址不自增,存储器地址自增;传输方向由外设站点到计数器站点;传输计数器初值与转换通道数相同;如果ADC为单次扫描,则DMA可不自动重装,如果为连续扫描,则DMA可自动重装;DMA转运时机需和ADC单个通道转换完成同步,因此触发源为ADC硬件触发。

接线图

接线同实验17。

单次扫描-单次转运

ADC驱动

ADC驱动沿用实验17。

AD.h

#ifndef __AD_H
#define __AD_H//供外部调用的SRAM数组
extern uint16_t AD_Value[4];void AD_Init(void);
void AD_GetValue(void);#endif

AD.c

#include "stm32f10x.h"uint16_t AD_Value[4];void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//配置多通道序列ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//配置通道数为4ADC_InitStructure.ADC_NbrOfChannel = 4;//开启扫描模式ADC_InitStructure.ADC_ScanConvMode = ENABLE;ADC_Init(ADC1, &ADC_InitStructure);//DMA初始化沿用实验18RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);DMA_InitTypeDef DMA_InitStructure;//外设站点//地址为ADC1的DR寄存器地址DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//数据宽度为半字DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//地址不自增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//存储器站点//地址为SRAM数组地址DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//数据宽度为半字DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输数量为4DMA_InitStructure.DMA_BufferSize = 4;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//不使用软件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//ADC1的硬件触发只与通道1连接,其余通道不行DMA_Init(DMA1_Channel1, &DMA_InitStructure);DMA_Cmd(DMA1_Channel1, ENABLE);//开启ADC到DMA的输出信号ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while (ADC_GetResetCalibrationStatus(ADC1));ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1));
}void AD_GetValue(void)
{//重写传输计数器DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, 4);DMA_Cmd(DMA1_Channel1, ENABLE);//启动转换ADC_SoftwareStartConvCmd(ADC1, ENABLE);//等待DMA转运完成while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));DMA_ClearFlag(DMA1_FLAG_TC1);
}

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "AD.h"int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while(1){AD_GetValue();OLED_ShowNum(1, 5, AD_Value[0], 4);OLED_ShowNum(2, 5, AD_Value[1], 4);OLED_ShowNum(3, 5, AD_Value[2], 4);OLED_ShowNum(4, 5, AD_Value[3], 4);Delay_ms(100);}
}

连续扫描-循环转运

ADC驱动

AD.h

#ifndef __AD_H
#define __AD_Hextern uint16_t AD_Value[4];void AD_Init(void);#endif

AD.c

#include "stm32f10x.h"uint16_t AD_Value[4];void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);ADC_InitTypeDef ADC_InitStructure;//使用ADC连续模式ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;ADC_InitStructure.ADC_NbrOfChannel = 4;ADC_InitStructure.ADC_ScanConvMode = ENABLE;ADC_Init(ADC1, &ADC_InitStructure);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_BufferSize = 4;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//使用DMA循环模式DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//ADC1的硬件触发只与通道1连接,其余通道不行DMA_Init(DMA1_Channel1, &DMA_InitStructure);DMA_Cmd(DMA1_Channel1, ENABLE);//开启ADC到DMA的输出信号ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while (ADC_GetResetCalibrationStatus(ADC1));ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1));//ADC触发放在初始化最后,之后ADC连续转换,DMA循环转运ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
//不需要GetValue函数

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "AD.h"int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while(1){//不需要调用GetValue函数OLED_ShowNum(1, 5, AD_Value[0], 4);OLED_ShowNum(2, 5, AD_Value[1], 4);OLED_ShowNum(3, 5, AD_Value[2], 4);OLED_ShowNum(4, 5, AD_Value[3], 4);Delay_ms(100);}
}

相关文章:

  • SAP ECCS标准报表在报表中不存在特征CG细分期间 消息号 GK715报错分析
  • 苍穹外卖菜品图片保存本地代码修改
  • 【多线程-第四天-NSCache Objective-C语言】
  • Jetpack Compose 实现主页面与局部页面独立刷新的最佳实践
  • 四六级听力调频广播有线传输无线覆盖系统:弥补单一发射系统安全缺陷,构建稳定可靠听力系统平台
  • Hadoop集群部署教程-P3
  • udhcpc和udhcpd的区别
  • vue3+element-plus实现省市区三级地址多选
  • 【技术派后端篇】ElasticSearch 实战指南:环境搭建、API 操作与集成实践
  • 如何在 Kali 上解决使用 evil-winrm 时 Ruby Reline 的 quoting_detection_proc 警告
  • DeepSeek模型剪枝策略是什么?如何让AI更轻更快更聪明!
  • 从零到一:网站设计新手如何快速上手?
  • GRPO训练器 文档
  • argparse
  • LeetCode 第54~55题
  • CentOS 操作系统下搭建 tsung性能测试环境
  • TCP实现多线程远程命令执行
  • TCP粘包:数据为何‘难舍难分’?拆解底层原理与实战解决方案
  • 解释`new`关键字的执行过程,并手动实现一个`myNew`函数。
  • Vue快速入门
  • 关于我们做网站/广州网站关键词推广
  • 中文网站建设模板下载/建立网站的主要步骤
  • 甘肃病毒感染最新消息/百度seo排名工具
  • 免费模板建设网站/关于软文营销的案例
  • 做网站需要解析吗/百度在线客服
  • 执法局网站建设目的/百度保障中心人工电话