简介:面向使用 TI TMS320C281x 系列 DSP 的嵌入式开发者与学习者,这套头文件包服务于 CCS 环境下的寄存器访问、外设驱动与底层程序编写。压缩包共 21 个文件,约 38KB,以 16 个 .h 头文件为主体,另有 2 个 .gel 调试辅助脚本、2 个 .cmd 链接命令文件和 1 个 .c 源文件;头文件涵盖 CPU 寄存器、PIE 中断控制、GPIO、ADC、SPI、SCI、eCAN、事件管理器等模块的声明与访问宏,便于按外设组织工程。已有 524 人学习,适合刚接触 2812/281x 平台、需要快速补齐工程基础文件的读者。其价值在于提供可直接纳入工程的设备头文件集合,配合调试脚本与链接配置,便于理解寄存器组织、外设初始化及中断向量设置,减少从零整理底层定义的时间,也为后续驱动移植和算法验证打下基础。
1. DSP281x头文件包:别急着编译,先看清它替你省掉了哪几百行寄存器地址
手头有一块2812板子,CCS里新建空工程,编译能过,但一写外设就卡住:寄存器地址从哪来、结构体怎么对齐、中断向量表放哪、CMD文件怎么配。DSP281x头文件包就是把这些脏活提前打包:281x系列的外设寄存器映射、位域结构体、启动分支、默认中断和链接命令文件,基本都按工程可用的形式放好。它适合刚上手2812、不想从零抄地址的人,也适合维护老281x工程、需要快速对照寄存器定义的开发者。它不是芯片手册的替代品,但能让你少翻几十页表,先把第一个GPIO跑起来。
2. 拆包:寄存器结构体、外设头文件与CMD的依赖链怎么读
DSP281x头文件包最容易让人翻车的地方,不是代码写错,而是把它当成普通C库:看到一个DSP281x_Device.h就把它塞进工程,编译一过就以为通了。实际上这份包的核心价值在寄存器映射层:每个外设用一个结构体描述寄存器布局,再用一个宏把结构体指针钉到固定基地址,最后靠CMD文件保证链接后的变量和代码段落在2812能访问的内存里。读包顺序应该反过来:先看目录分层,再看结构体指针,最后看最小依赖链。直接翻main.c很容易把示例里的板级配置当成头文件自带的能力。
2.1 先按四层目录看,不要从main开始翻
拿到包后我一般先在根目录跑一遍文件分类,不急着打开CCS。常见做法是把它拆成四层:设备头文件层、外设头文件层、外设源文件层、链接与启动层。不同包的文件名可能有差异,但类别基本固定。用find把文件按后缀和关键词列出来,比在IDE里逐个点开快得多。
# 在头文件包根目录执行:按类别列出关键文件 find . -maxdepth 3 -type f \( -name "*.h" -o -name "*.c" -o -name "*.asm" -o -name "*.cmd" \) \ | sort | sed 's#^./##'逻辑说明:这条命令只做三件事,限制三层目录、筛出C语言和链接相关文件、排序后去掉./前缀。参数说明:-maxdepth 3防止包内示例目录太深导致输出刷屏;-name后面的后缀按你包内实际存在的类型增减;如果包内还有.lib或.obj,不要急着加进来,先看清源码层。输出后按文件名里的Device、Gpio、CpuTimers、Pie、DefaultIsr、CodeStartBranch、.cmd分组,基本就能画出依赖关系。
| 文件类别 | 常见命名特征 | 在工程里的作用 |
|---|---|---|
| 设备头文件 | DSP281x_Device.h | 汇总CPU头、外设头、全局宏 |
| 外设头文件 | DSP281x_Gpio.h、DSP281x_Adc.h | 寄存器结构体、位域、外设宏 |
| 外设源文件 | DSP281x_Gpio.c、DSP281x_CpuTimers.c | 初始化函数、延时、默认配置 |
| 中断与启动 | DSP281x_DefaultIsr.c、DSP281x_CodeStartBranch.asm | 默认ISR、复位后跳转 |
| 链接命令 | DSP281x_Headers_nonBIOS.cmd、F2812.cmd | 段映射、寄存器文件占位 |
提示:目录里如果同时出现多个CMD文件,不要一次性全加进工程。先确认哪个是寄存器头文件占位用的,哪个是代码段映射用的,后面第3章会拆开讲。
2.2 寄存器结构体指针怎么读:基地址、成员偏移与volatile
外设头文件里最值钱的是结构体定义。它把某个外设的一串寄存器按手册偏移排好,每个成员再用位域拆到bit。比如GPIO控制寄存器,常见写法是先定义一个GPIO_CTRL_REGS结构体,成员按GPAMUX、GPADIR、GPASET、GPACLEAR等顺序排列,然后用一个宏把指针指到外设基地址。读的时候重点看三处:结构体成员顺序、指针宏的基地址、成员是否带volatile。
/* 简化示意:实际成员名和顺序以包内头文件为准 */ typedef struct { Uint16 GPAMUX; // GPIO A 复用控制 Uint16 GPADIR; // GPIO A 方向 Uint16 GPASET; // 置位 Uint16 GPACLEAR; // 清除 Uint16 GPATOGGLE; // 翻转 } GPIO_CTRL_REGS; #define GpioCtrlRegs ((volatile GPIO_CTRL_REGS *)0x007000)逻辑说明:结构体负责“偏移”,宏负责“基地址”,volatile负责告诉编译器每次访问都去内存里读,不要缓存到寄存器。参数说明:0x007000只是占位示意,实际基地址以包内头文件和芯片手册为准,不要照抄这段地址进工程;Uint16通常来自设备头文件里的类型定义,如果编译报未知类型,说明DSP281x_Device.h没包含进来。成员顺序不能随手调整,调换两个成员会让所有位域访问错位,现象是写A寄存器却改了B寄存器,排查时非常像玄学。
注意:位域结构体依赖编译器的位域布局。换编译器或换优化等级后,如果发现位域写入异常,先回到
-O0确认,再检查头文件是否被重复定义。
2.3 最小依赖链:Device.h到外设头文件
最小依赖链一般从DSP281x_Device.h开始。它负责把CPU寄存器头、外设头文件和全局类型串起来;DSP281x_Examples.h更偏板级配置,放时钟频率、延时宏、外设时钟使能声明。很多新手把这两个头文件当成二选一,结果不是缺类型就是缺函数声明。正确做法是:设备头必须保留,示例头按工程需要保留;外设源文件只编译你实际用到的,不要把所有.c都拖进来。
/* 典型包含链,文件名以包内实际为准 */ DSP281x_Device.h ├─ DSP281x_CpuTimers.h ├─ DSP281x_PieCtrl.h ├─ DSP281x_PieVect.h ├─ DSP281x_Gpio.h ├─ DSP281x_Adc.h └─ DSP281x_GlobalPrototypes.h DSP281x_Examples.h ├─ 系统时钟宏 ├─ DELAY_US声明 └─ 板级初始化函数声明逻辑说明:Device.h解决“寄存器长什么样”,Examples.h解决“这块板子怎么初始化”。参数说明:如果你只做GPIO翻转,可以暂时不编译ADC和SCI的源文件,但Device.h里对外设头的包含不要删,删了容易在后续加外设时漏声明。常见误用是只包含外设头、不包含设备头,编译能过但Uint16、EALLOW、EDIS这些宏找不到;另一种误用是把所有源文件都加进工程,导致默认ISR和示例初始化函数重复定义,链接阶段才会报错。
3. 在CCS里搭2812工程:包含路径、启动分支与第一个GPIO翻转
把包拆明白后,下一步不是马上写业务代码,而是先搭一个能稳定编译、能下载、能看寄存器的空工程。2812的CCS工程有三个地方最容易出问题:包含路径用了绝对路径、CMD文件和运行方式不匹配、启动分支没进工程。工程搭对了,后面加定时器和ADC就是顺水推舟;工程搭不对,每个外设都会像黑匣子,编译过了也不代表跑得对。
3.1 新建工程与include路径
我一般把包放到工程目录下的third_party/dsp281x_headers,不放到CCS安装目录,也不引用桌面路径。这样换机器或换目录时,只需要改工程属性里的一个根路径。先在工程根目录确认目录结构,再进CCS建空工程,芯片选2812对应的型号,输出格式按你的下载器要求选。包含路径至少加三个:包根目录、设备头所在目录、外设头所在目录。
# 在工程根目录检查头文件包是否放对位置 test -d third_party/dsp281x_headers || echo "头文件包目录不存在" find third_party/dsp281x_headers -maxdepth 2 -name "DSP281x_Device.h" -print逻辑说明:第一条判断目录是否存在,第二条确认设备头文件在不在预期层级。参数说明:third_party只是我习惯的第三方目录名,你可以换成drivers或headers,但不要带中文和空格;-maxdepth 2够用,设备头一般在包根或include子目录。输出为空说明包放错层了,先别进CCS,先把目录摆正。
/* 工程属性里的包含路径示意,不是C代码,按CCS菜单填 */ ${PROJECT_ROOT}/third_party/dsp281x_headers ${PROJECT_ROOT}/third_party/dsp281x_headers/include ${PROJECT_ROOT}/third_party/dsp281x_headers/source逻辑说明:CCS的包含路径按“根目录→头文件目录→源文件目录”三层加,编译器找头文件时按顺序搜索。参数说明:${PROJECT_ROOT}是工程变量,换机器不用改绝对路径;如果包内头文件直接平铺在根目录,第二行可以删掉。不要用C:\Users\...这种绝对路径,换一台机器就翻车,血泪经验是工程归档时最先坏的就是绝对路径。
3.2 启动分支和CMD文件怎么选
2812上电后从复位向量开始跑,启动分支负责跳到C运行入口。头文件包里常见的DSP281x_CodeStartBranch.asm就是这段跳转,它需要和CMD文件里的段名配合。CMD文件则决定代码放Flash还是RAM、寄存器文件段放哪、堆栈放哪。RAM调试和Flash运行要用不同的CMD,不要同时加两个,也不要把RAM版CMD直接烧进Flash。
/* CMD片段示意:段名和地址以你包内文件为准 */ MEMORY { PAGE 0: PRAMH0 : origin = 0x3F8000, length = 0x001000 PAGE 0: FLASH : origin = 0x3F0000, length = 0x020000 } SECTIONS { .text : > FLASH, PAGE = 0 .cinit : > FLASH, PAGE = 0 .stack : > PRAMH0, PAGE = 0 .ebss : > PRAMH0, PAGE = 0 }逻辑说明:MEMORY定义物理内存块,SECTIONS把编译出来的段放进去。参数说明:origin和length必须和2812实际内存映射一致,不要照抄示意地址;.text放代码,.cinit放初始化表,.stack放堆栈,.ebss放未初始化全局变量。RAM调试时把.text指到RAM能加快下载,但掉电丢失;Flash运行要保留启动分支,并确认CMD里包含复位向量段。常见错误是只加CMD不加启动汇编,链接能过,下载后跑飞。
提示:工程里只保留一个主CMD文件。寄存器头文件占位用的CMD如果和代码段CMD同时出现,链接器可能把寄存器结构体放到错误页,现象是读写外设全错。
3.3 第一个GPIO翻转:从寄存器到示波器
工程能编译后,先写一个最小GPIO翻转,不要一上来就上中断。这个例子能同时验证系统时钟、外设时钟、GPIO复用、方向寄存器和延时函数。代码里用EALLOW/EDIS包住受保护寄存器,用GPASET/GPACLEAR做原子置位和清除,延时用包内提供的DELAY_US。
#include "DSP281x_Device.h" #include "DSP281x_Examples.h" void main(void) { InitSysCtrl(); // 系统时钟、外设时钟、看门狗 EALLOW; // 允许写受保护寄存器 GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0; // GPIO0 复用为普通IO GpioCtrlRegs.GPADIR.bit.GPIO0 = 1; // GPIO0 输出 EDIS; for(;;) { GpioDataRegs.GPASET.bit.GPIO0 = 1; // 置高 DELAY_US(500000); // 约0.5秒 GpioDataRegs.GPACLEAR.bit.GPIO0 = 1; // 置低 DELAY_US(500000); } }逻辑说明:InitSysCtrl()负责把系统时钟和外设时钟打开,EALLOW/EDIS是281x写保护寄存器的开关,GPAMUX1把引脚从外设功能切回普通IO,GPADIR设输出,GPASET/GPACLEAR写1生效、写0无效,避免读改写竞争。参数说明:DELAY_US的参数单位是微秒,500000约0.5秒,但实际延时依赖CPU频率宏和DSP281x_usDelay.asm是否进工程;如果示波器测到频率差很多,先查系统时钟配置,再查延时宏。不同封装的GPIO数量不同,GPIO0不一定引到排针,先看板子原理图。
4. 外设寄存器级写法:定时器、PIE中断与ADC采样怎么套头文件
GPIO跑通后,头文件包的价值才真正体现:定时器、PIE、ADC这些外设的寄存器定义和初始化函数已经摆好,你只需要按“时钟—配置—使能—应答”的顺序填参数。这里不追求把每个外设都写全,而是挑三个最常用的:定时器验证时基,PIE验证中断链路,ADC验证采样通道。三个都跑通,基本就能判断这份包和你的板子合不合。
4.1 定时器:CpuTimer0的周期、预分频与中断标志
2812的CPU定时器是32位,头文件包里一般有DSP281x_CpuTimers.c,提供InitCpuTimers()和ConfigCpuTimer()。配置时重点看三个参数:CPU频率、期望周期、预分频。周期寄存器算错了,现象是中断进得太快或太慢;预分频没设对,长周期会溢出。
#include "DSP281x_Device.h" #include "DSP281x_Examples.h" void timer0_setup(void) { InitCpuTimers(); // 初始化三个CPU定时器 ConfigCpuTimer(&CpuTimer0, 150.0, 1000000); // 150MHz,1秒周期 CpuTimer0Regs.TCR.bit.TSS = 0; // 启动定时器 }逻辑说明:InitCpuTimers()把定时器寄存器恢复到可控状态,ConfigCpuTimer()按频率和微秒数算周期寄存器,TSS=0启动计数。参数说明:第二个参数填CPU主频,单位MHz,必须和实际系统时钟一致;第三个参数填周期,单位微秒,1000000就是1秒。如果主频填错,定时器不会报错,只是周期不准,这种问题用示波器看中断引脚最直接。
4.2 PIE中断:向量表、使能与应答
2812的中断走PIE,外设中断先到PIE组,再到CPU。头文件包里通常有DSP281x_PieCtrl.h、DSP281x_PieVect.h和DSP281x_DefaultIsr.c。配置顺序是:复制向量表、注册ISR、使能PIE组、使能CPU中断、全局使能。ISR里必须写PIEACK,否则同组中断只进一次。
#include "DSP281x_Device.h" #include "DSP281x_Examples.h" interrupt void cpu_timer0_isr(void) { CpuTimer0.InterruptCount++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 应答PIE第1组 } void pie_setup(void) { EALLOW; PieVectTable.TINT0 = &cpu_timer0_isr; // 注册定时器0中断 EDIS; PieCtrlRegs.PIECTRL.bit.ENPIE = 1; // 使能PIE PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // 使能PIE第1组第7个 IER |= M_INT1; // 使能CPU中断1 EINT; // 全局中断 ERTM; // 实时调试中断 }逻辑说明:PieVectTable是PIE向量表,注册的函数地址会在中断发生时被调用;PIEIER1.INTx7对应定时器0,具体组号以包内定义为准;IER是CPU中断使能,EINT和ERTM打开全局中断和实时调试。参数说明:PIEACK_GROUP1要和PIEIER1对应,组号写错会表现为中断只进一次或者完全不进;M_INT1来自设备头文件,不要手写数字。ISR里不做浮点长运算,先只做计数和应答,排除中断链路问题后再加业务。
4.3 ADC采样:结果寄存器、触发源与排序器
2812的ADC是12位,结果寄存器有左对齐和右对齐两种读法,头文件包里一般用宏或位域区分。初始化顺序是:使能ADC时钟、配置采样窗口、配置排序器、启动转换、等标志、读结果。不要一上来就用中断模式,先用轮询确认通道和结果寄存器对得上。
#include "DSP281x_Device.h" #include "DSP281x_Examples.h" Uint16 adc_read_ch0(void) { InitAdc(); // ADC时钟和校准 AdcRegs.ADCTRL1.bit.ACQ_PS = 0xF; // 采样窗口 AdcRegs.ADCTRL3.bit.ADCCLKPS = 3; // ADC时钟分频 AdcRegs.ADCTRL2.bit.SOC_SEQ1 = 1; // 软件启动排序器1 while(AdcRegs.ADCST.bit.INT_SEQ1 == 0) {} AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1; // 清中断标志 return (AdcRegs.ADCRESULT0 >> 4); // 右对齐取12位 }逻辑说明:InitAdc()打开ADC时钟并做必要校准,ACQ_PS拉长采样窗口,ADCCLKPS分频到ADC允许范围,SOC_SEQ1软件触发,INT_SEQ1轮询转换结束,最后右移4位取12位结果。参数说明:ACQ_PS=0xF是保守值,采样窗口越长越稳但速度越慢;ADCCLKPS=3只是示意,实际分频要按系统时钟算,超过ADC上限会采样异常;>>4对应右对齐,如果包内配置成左对齐,结果要换算法。先用手册确认结果寄存器对齐方式,再写换算。
5. 避坑与排查:链接报错、寄存器写不进去、中断不进门的现场记录
头文件包本身不复杂,复杂的是它和CCS工程、CMD、芯片状态之间的配合。下面这几条是281x工程里最容易反复出现的翻车点,每条都按“现象—原因—解决”记,方便你下次直接对照。
5.1 链接报错:program will not fit into available memory
现象:编译通过,链接时报program will not fit into available memory,或者某个段被放到不存在的地址。原因通常不是代码太大,而是CMD文件把.text指到了RAM小段,或者同时加了两个CMD导致段映射冲突。解决:先看.map文件里哪个段超了,再确认工程里只有一个主CMD;RAM调试换RAM版CMD,Flash运行换Flash版CMD,启动分支和CMD要配套。
# 在工程输出目录查找map文件里的段占用 grep -E "\.text|\.cinit|\.ebss|\.stack" *.map | head -40逻辑说明:grep把关键段的行筛出来,head限制输出避免刷屏。参数说明:*.map要换成你工程实际生成的map文件名;如果输出里.text的origin落在小RAM块,基本就是CMD选错。改CMD后先清理工程再重新编译,避免旧对象文件残留。
5.2 寄存器写不进去:Memory Browser里一直是0
现象:代码里明明写了GpioCtrlRegs.GPADIR.bit.GPIO0 = 1,CCS的Memory Browser看寄存器还是0。原因常见两种:受保护寄存器没加EALLOW/EDIS,或者外设时钟没开。解决:先确认InitSysCtrl()被调用,再确认写保护开关包住了寄存器写操作。281x的GPIO复用和方向寄存器不少是受保护的,漏掉EALLOW不会编译报错,只会静默失败。
EALLOW; GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0; GpioCtrlRegs.GPADIR.bit.GPIO0 = 1; EDIS;逻辑说明:EALLOW清写保护,EDIS恢复保护,中间只放必要的寄存器写。参数说明:不要在EALLOW和EDIS之间放长循环或函数调用,避免意外改写其他保护寄存器;如果外设时钟没开,写进去也可能读回0,先查系统控制里的外设时钟使能位。
5.3 中断不进:ISR只进一次或者完全没反应
现象:定时器开始计数,但ISR不进;或者进了一次就再也不进。原因通常是PIEACK没应答、PIE组使能没开、向量表没注册,或者全局中断没打开。解决:按“向量表→PIE组→CPU中断→全局中断→ISR应答”顺序逐项查。先看PieVectTable里函数地址是不是你的ISR,再看PIEIER对应位,最后看ISR里有没有写PIEACK。
interrupt void cpu_timer0_isr(void) { CpuTimer0.InterruptCount++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 漏掉这一句,同组中断不再进 }逻辑说明:PIEACK是应答位,写对应组号才能让PIE继续转发同组中断。参数说明:组号必须和PIEIER使能的组一致,PIEACK_GROUP1对应第1组;如果用了多个外设中断,每个ISR都要应答自己所属组。调试时先在ISR里翻转一个GPIO,用示波器看有没有进,比打印更可靠。
5.4 变量值异常:优化后全局变量被改或读不到
现象:调试时某个标志位在ISR里改了,主循环读不到;或者一开优化,寄存器写入顺序变了。原因通常是变量没加volatile,或者优化等级过高把外设访问优化掉了。解决:中断和主循环共享的变量加volatile,调试阶段先用-O0,确认逻辑后再逐级开优化。外设寄存器本身在头文件里通常已经带volatile,自己定义的共享变量不要漏。
volatile Uint16 g_timer_tick = 0; interrupt void cpu_timer0_isr(void) { g_timer_tick++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; }逻辑说明:volatile告诉编译器每次从内存读写,不要缓存到寄存器。参数说明:g_timer_tick这种全局标志适合用Uint16或Uint32,不要在中断里做浮点累加;如果主循环里做g_timer_tick++,要意识到它不是原子操作,必要时关中断保护。
5.5 延时不准:DELAY_US跑出来差几倍
现象:DELAY_US(1000)在示波器上不是1ms,可能是几倍或几分之一。原因通常是CPU频率宏和实际系统时钟不一致,或者DSP281x_usDelay.asm没进工程。解决:先确认系统时钟配置,再确认延时汇编文件被编译,最后检查CPU_RATE一类宏是否和主频匹配。延时函数不是精密定时器,长延时用CPU定时器更稳。
/* 确认工程里包含延时汇编,且CPU频率宏与实际一致 */ DELAY_US(1000); // 参数单位微秒,依赖CPU_RATE逻辑说明:DELAY_US是循环延时,靠CPU频率算循环次数。参数说明:参数是微秒,但精度受中断和优化影响;如果工程里开了中断,长延时会被打断,测出来的时间偏长。需要精确周期时,用第4章的CPU定时器,不要用延时函数硬扛。
6. 进阶核对:用Memory Browser和反汇编验证头文件映射是否对得上
头文件包用久了,最容易产生一种错觉:编译过了,外设也动了,映射就一定对。实际上不同包的文件名可能相似,寄存器结构体却可能按不同芯片变体裁剪过。我一般会做一次最小核对:在CCS里用Memory Browser看外设基地址附近的值,再用反汇编看一条寄存器写操作有没有落到预期地址。两步都对上,后面写业务代码才踏实。
6.1 用Memory Browser核对基地址和位域
先在Expressions窗口里输入&GpioCtrlRegs,看它解析出来的地址;再到Memory Browser里跳到这个地址,手动写一个方向位,观察相邻寄存器有没有被误改。这个动作能发现结构体成员顺序错位、基地址偏移、位域跨字等问题。
/* 在CCS Expressions/Memory Browser里做的核对,不是C代码 */ &GpioCtrlRegs -> 查看结构体基地址 GpioCtrlRegs.GPADIR.all -> 查看方向寄存器整字 GpioCtrlRegs.GPAMUX1.all -> 查看复用寄存器整字逻辑说明:先看指针地址,再看整字寄存器,最后看位域。参数说明:地址值以你工程链接后的实际值为准,不要和示例比;如果&GpioCtrlRegs落在代码段或栈区,说明CMD把寄存器段放错了。手动写位域时只改一位,读回整字确认其他位没变,能排除位域掩码错误。
6.2 用反汇编看寄存器访问是否落到预期地址
Memory Browser看的是结果,反汇编看的是过程。把GPIO配置函数编译后打开反汇编,找到写GPADIR的那条指令,确认它的目标地址和Memory Browser里的地址一致。如果反汇编里出现的是栈上临时变量,说明编译器把寄存器访问优化成了普通内存写,回头查volatile和头文件定义。
; 反汇编片段示意:目标地址以实际工程为准 ; MOV @AL, #0x0001 ; 写GPADIR的某一位 ; 在Disassembly窗口核对目标地址是否等于&GpioCtrlRegs + 偏移逻辑说明:反汇编里看的是“写到哪里”,不是“写了什么值”。参数说明:不同优化等级生成的指令不同,先用-O0核对,再开优化;如果地址是间接寻址,顺着指针寄存器往前看一条MOV,确认基地址加载正确。
从那以后我每次拿到新的281x头文件包,都先按第2章的目录清单核对一遍,再按第6章做一次基地址和反汇编对照,最后才上电跑GPIO,不再一上来就编译下载。这份DSP281x头文件包按你平时拿资源的渠道取就行,拿到后先跑目录核对,再进CCS建工程,能少走不少弯路。希望帮到你。
本文还有配套的精品资源,点击获取