DSP学习预备知识——C语言和最小系统板介绍
1.C语言知识
1.1 define 和typedef
\qquad #define(宏定义)只是简单的字符串代换(原地扩展),它本身并不在编译过程中进行,而是在预处理过程就已经完成了。typedef是为了增加可读性而为标识符另起的新名称,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能,它是语言编译过程的一部分,但它并不实际分配内存空间。define和typedef对指针操作不同,作用域等都不同。一般在控制器编程中将地址、常量define成一个变量标识符。而typedef一般用于定义类型的别名。
1.2. struct、union和enum
\qquad 结构体struct各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和;
#include "stdio.h"
int main (void)
{
struct A {
int a; //8 size
double b;//8 size
char c; //4 size
char d; //4 size
};//one way to declare struct
struct A AA;
struct {
struct A AA; //24 size
int a; //4 size
char c; //4 size
double b; //8 size
}B;//another way to declare struct
typedef struct{
int a; //4 size
char c[10]; //4 size + 8 size
double d; //8 size
char* e; //8 size
}STRUCT;//use typedef to declare struct
STRUCT stu;
struct BIT{
char IO1:1;//1 bit
char IO2:1;//1 bit
char IO6:7;//over 8 bit then start another byte
};
struct BIT bitter;
printf("%d\n",sizeof(AA)); //24
printf("%d\n",sizeof(B)); //40
printf("%d\n",sizeof(stu));//32
printf("%d\n",sizeof(bitter));//2
return 0;
}
\qquad 联合体union 各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,不允许对联合体变量名直接赋值或其他操作。union和struct声明类似。
#include "stdio.h"
int main (void)
{
union u{
int a[10];
int b;
double c;
};//one way to declare union
struct reg{
int a:1;//count 0 bit
int b:6;//count 1 to 6 bit
};
typedef union{
int all;
struct reg bit;//bit is declared as struct type
}Typeu;//use typedef to declare union
union u var;
var.b = 4;
Typeu tu;
tu.bit.a = 1;
printf ("%d,\t%d\n",sizeof(var),var.a[0]);//40 4
printf ("%d,\t%d",sizeof(tu),tu.bit.a);//4 -1
return 0;
}
\qquad 枚举型enum是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。声明的没经验类型是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。也可以人为设定枚举成员的值,从而自定义某个范围内的整数。
#include <stdio.h>
int main (void)
{
enum SPEED{
IO_10MHz = 1,//use ',' to separate each member
IO_20MHz,
IO_50MHz,
};//one way to declare enum
enum SPEED speed;//enum type is same size as int
typedef enum {
IO_AIN = 0x00,
IO_IPD = 0x28,
IO_PP = 0x10,
}IOMODE;//use typedef to declare enum
printf("%d\t %d\n",sizeof(speed),IO_20MHz);// 4 2
printf("%d\t %d\n",sizeof(IOMODE),IO_PP);//4 16
return 0;
}
\qquad 理解了上述例子,来看下面的例子,猜猜输出是什么
#include "stdio.h"
int main (void)
{
enum GPIO{
GPIO0 = 0x1111,
GPIO1,GPIO2,GPIO3,
};
struct regbits{
unsigned int a : 1;
unsigned int b : 4;
unsigned int c : 8;
};
typedef union{
unsigned int all;
struct regbits bit;//bit is declared as struct type
}Typeu;
typedef struct {
unsigned int a;
Typeu REGS;
}reg;
reg Regs;
Regs.REGS.all = GPIO3;
printf ("%d,\t%d\n",sizeof(Regs),Regs.REGS.bit.b);//8 'b'1010->10
printf ("%d\n,",Regs.REGS.bit.c);//'b'1000 1000->136
return 0;
}
\qquad 查找一个外设的寄存器如下(学一个外设,可以按照下面的方式去逐级查找)
#include <stdio.h>
#define Uint16 unsigned int
int main (void)
{
struct PLLSTS_BITS { // bits description
Uint16 PLLLOCKS:1; // 0 PLL lock status
Uint16 rsvd1:1; // 1 reserved
Uint16 PLLOFF:1; // 2 PLL off bit
Uint16 MCLKSTS:1; // 3 Missing clock status bit
Uint16 MCLKCLR:1; // 4 Missing clock clear bit
Uint16 OSCOFF:1; // 5 Oscillator clock off
Uint16 MCLKOFF:1; // 6 Missing clock detect
Uint16 DIVSEL:2; // 7 Divide Select
Uint16 rsvd2:7; // 15:7 reserved
};
union PLLSTS_REG {
Uint16 all;
struct PLLSTS_BITS bit;
};
struct SYS_CTRL_REGS {
Uint16 rsvd7; // 0
union PLLSTS_REG PLLSTS; // 1
};
struct SYS_CTRL_REGS SysCtrlRegs;
SysCtrlRegs.PLLSTS.bit.PLLLOCKS= 1;
printf("%d,\t%d",sizeof(SysCtrlRegs),SysCtrlRegs.PLLSTS.bit.PLLLOCKS);
return 0;
}
\qquad
下图显示了寄存器的表达方式。
1.3 extern、volatile
\qquad
在嵌入式系统编程过程中,extern表明变量或者函数是定义在其他其他文件中的。用法有几点需要注意:(a)一个c文件需要调用另一个c文件里的变量或者函数,而不能从.h文件中调用变量;(b)一般用extern声明变量时不要给其赋值,最好在定义这个变量的时候声明,若定义该变量时无法确定其初始值,可以令其等于0,或避免使用extern关键字,采用地址传参。(c)引用函数和引用变量是一样的,如果需要调用其他.c文件中的函数,在文件中的函数声明前加extern即可,不加extern而直接声明函数也可以,因为声明全局函数默认前面带有extern;(d)如果不想让其他.c文件引用本文件中的变量,加上static即可;
\qquad
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触发,比如声明一个结构体变量时(volatile struct EPWM_REGS EPwm1Regs;)常常需要添加关键字volatile;
2.最小系统板设计
2.1 电源部分
\qquad
DSP芯片内部的电压较多,一般可以分为IO口电压、内核电压、ADC相关电压等,具体细分如下:(a)IO口电压VDDIO(3.3V);(b)芯片内核电压VDD(1.9V);(c)ADC模拟电源引脚VDDA2,VDDAIO(3.3V);(d)ADC模拟IO电源引脚;(e)ADC模拟电源电源引脚VDD1A18,VDD2A18(1.9V)。接地引脚(a)芯片接地VSS;(b)ADC模拟IO电源接地引脚VSSAIO;(c)ADC模拟接地引脚VSS1AGND,VSS2AGND等;其中,各个3.3V,1.9V,GND引脚都可以各自连接到一起,但为了走线时分辨好回路,电源一般用磁珠隔离,地线用0Ω隔离。
\qquad
电源部分采用TD6821芯片,芯片特点:双通道输出SOIC-8封装、1.5MHz同步降压、输出电流达1.5A、输入电压范围3V-5.5V、内部开关管的Rds(on)较小,内部软起动和限制浪涌电流等。注意到上面说的输入电压范围3V-5.5V,显然这时候需要再输入端接一个保护电路来保护TD6821和后面的主控芯片。电源部分电路如图:
\qquad
当输入小于齐纳二极管MM1Z5B1的钳位电压5.1V时,Q2管不导通,PMOS的g级被下拉到地,输入VIN经过PMOS到电源芯片输入;当输入过压时,Q2管导通,PMOS的g级和s级等电位(忽略PNP的导通压降),此时PMOS不通,VIN无法进入电源芯片输入脚,保护了电源芯片和后续电路。通上面的分析,可以知道,PMOS需要选一个导通压降低,Rds(on)低的PMOS较好。后续的输出电压按照芯片手册上的公式
V
o
1
,
o
2
=
(
1
+
R
f
2
R
f
1
)
×
V
F
B
V_{o1,o2} \ = \ \ (1+\frac{Rf2}{Rf1})\times V_{FB}
Vo1,o2 = (1+Rf1Rf2)×VFB
2.2 时钟部分
\qquad
如果不使用片上振荡器,时钟由X1X2或XCLKIN引脚上的外部时钟源输入产生。如果从XCLKIN引脚接入时钟,那么X1保持地电平,X2浮空,外部接入一个3.3V的时钟源。如果从X1和X2接入时钟源,那么XCLKIN保持低电平,X1接外部时钟信号,X2仍然浮空。
\qquad
总的来说,这里OSC电路可以通过在X1和X2之间接入一个晶振来产生(并最好保持XCLKIN接地)。如果没有在X1和X2之间接入晶振,那么就需要保持X1接地,X2浮空,并且在XCLINK之间接入振荡器。注意晶振(Crystal)、共振器(Resonator)、振荡器(Oscillator)的区别。可以简单的认为晶振和共振器是无源晶振(需要接外部电路才可以起振),而振荡器是有源晶振。那么最简单的方式就是在X1和X2之间接入晶振或者共振器,然后利用28335内部电路使得晶振起振。
\qquad
时钟源进来后,首先必须将OSCOFF位置1,这样才能使得时钟源进入直接选择器和经PLL模块到选择器。在PLL模块这里有一个PLLOFF位是用来关断PLL的,选择不关断(默认),接下来就是倍频环节PLLCR寄存器,低四位用来设置倍频。经过倍频到选择器输出后,设置寄存器PLLSTS的第8位和第7位DIVSEL来设置分频系数,在倍频设值之前必须将分频系数置零。通常我们的150MHz是OSCCLK(30MHz)
×
\times
× PLLCR(10)/DIVSEL(2)=150MHz;
2.3 IO口引出
\qquad 引出88个GPIO引脚和16个ADC输入引脚和系统时钟输出引脚CLKOUT基本上就可以了。加上滤波、Jtag、Reset、以及芯片手册上推荐的一些电阻电容就可以了。
2.4 tms320f28335与stm32f103zet6对比
tms320f28335 | stm32f103zet6 | |
---|---|---|
公司 | ti | st |
最高工作频率(MHz) | 150 | 72 |
位数(bit) | 32 | 32 |
定时数目 | 3 | 11 |
PWM个数 | 16 | 18 |
SRAM | 34k$\times$16 | 64k |
通信接口 | SCI、SPI、CAN、IIC等 | IIS、CAN、UART等 |
IO电压(V) | 3.3 | 2.0~3.6 |
ADC channel | 32 | 21 |
DMA channel | 6 | 12 |
DAC channel | 0 | 2 |
IO 个数 | 88 | 112 |
复用关系 | 重映射 | 复用器 |
仿真器 | XDS | stlink/jtag等 |
中断 |