对TMS320F28379D的寄存器进行操作的研究学习(本文以ADC寄存器为例,其他寄存器定义方式完全一样)

本文需要用到三个文件F2837xD_adc.h、F2837xD_GlobalVariableDefs.c、F2837xD_GlobalVariableDefs.c,在学习之前希望在CCS中将其打开便于学习,还有就是打开C2000助手来查看F28379D的各个寄存器。

1.前言

F28379D芯片的开发既支持C语言,也支持汇编语言,由于C语言形象一些,并且大多学校开设C语言这门课程,所以本次DSP程序采用C语言来对相关的寄存器进行相关的操作。

DSP寄存器能够实现对系统和外设功能进行相应配置和控制,是一种特殊的存储单元,它的每一位都有固定的含义或者具体的功能,对它的每一位进行赋值,系统就会根据所赋的值来运行或实现这一位所在的功能。换句话说就是,对DSP的开发就是对DSP的寄存器进行赋值(或控制)来实现DSP的相应功能,因此对寄存器的控制是否方便就决定了DSP的开发是否便捷。

F28379D在定义寄存器时采用了位定义、声明共同体和寄存器结构体的过程来配置相应寄存器。

本文以ADC数模转换的寄存器为例进行解释,查看C2000助手的F2837XD的ADC基地址,可以看到相应寄存器的存放地址,4个结果寄存器就存放采样后的结果,4个ADC寄存器里面存放的是控制相应ADC功能的寄存器(注意开始地址和结束地址,下文会用到)。

打开C2000助手的ADC寄存器里面其中一个ADC控制1寄存器(ADCCTL1),可以看到其占用了1个字,也就是16位,而每一位要么保留(不用),要么就是有相应的功能,这就是对寄存器进行了位定义、声明共同体和寄存器结构体后可以进行一系列的操作。

那什么是位定义、声明共同体、寄存器结构体呢?下面详细介绍这三个过程!!!

2.位定义

C语言中有一种数据结构叫位域;就是按位来划分区域。

把一个字节(8个二进制位)划分成若干个区域,如下图划分了三个区域,同时要声明每个区域所占用的位数(第一个区域占了2位,第二个区域占了3位,第三个区域占了3位),同时,每个区域都可以取域名(第一个区域为A,第二个区域为B,第三个区域为C);这就是位域的的划分过程,接下来在对位域进行操作时,就可以按照相应的域名进行操作。

位域定义的语法格式:

Struct 位域结构名
{
    类型说明符  位域名1:位域长度;
    类型说明符  位域名2:位域长度;
    ...

    类型说明符  位域名n:位域长度;
};
//注意:使用Struct就说明是结构体的数据类型,位域结构要取个名字;
//类型说明符就是基本的数据类型,可以是int,char 型等;
//位域名可以任意取,能够反映其位域的功能就好,位域长度是指这个位域是由多少个位组成的;
//和结构体定义一样,大括号最后的“:”不可缺少,否则会出错。

例:将一个名为bs字的16位划分成了3个位域,其中DO~D7共8位为位域a,D8~D9共2位为位域 b,D10~D15共6位为位域c,用位域的方式来定义:

struct bs
{
    int  a:8;
    int  b:2;
    int  c:6;
};
struct bs bs1;//声明bs型变量bs1;

位域是C语言中的一种数据结构,因此需要遵循先声明后使用的原则。上例声明了bs1,说明bs1是bs型的变量,共占2字节,其中位域a占8位,位域b占2位,位域c占6位。

关于位域的定义还有以下几点说明:
1.位域的定义必须按从右往左的顺序,也就是说得从最低位开始定义

2.一个位域必须存储在同一个字节中,不能跨2字节。如果一个字节所剩空间不够放另一域时,应该从下一个单元起存放该域,如下所示:

struct bs
{
    int  a:4;
    int   :0;//空域
    int  b:5; //从第2个字节开始存放
    int  c:3;
};

在这个位域定义中,第一个位域a占第一个字节的4位,而第2个位域b占5位,很显然第一个字节剩下的4位不能够完全容纳位域 b,所以第一个字节的后4位写0留空,b从第2个字节开始存放。

3.位域的长度不能大于一个字节的长度,也就是说一个位域不能超过8位。

4.位域可以无位域名,这时它只用作填充或调整位置。无名的位域不能使用,如下所示:

struct bs
{
    int  a:4;
    int   :2;//这两位不能使用
    int  b:2; 
    int  c:5;
    int  d:3;
};

前言ADC控制1寄存器(ADCCTL1)为例,打开headers_include中F2837xD_adc.h文件(本人上一个文章《构建TMS320F28379D新工程的步骤中建立的文件夹),这是官方给定义的所有ADC寄存器,可以看到ADCCTL1寄存器的位域定义方式如下,和前言的ADCCTL1寄存器那张图给的功能能够一一对应。

若没有看之前的文章,那就去自己安装的C2000ware中按照下面的路径去寻找:

struct ADCCTL1_BITS {                   // bits description
    Uint16 rsvd1:2;                     // 1:0 Reserved
    Uint16 INTPULSEPOS:1;               // 2 ADC Interrupt Pulse Position
    Uint16 rsvd2:4;                     // 6:3 Reserved
    Uint16 ADCPWDNZ:1;                  // 7 ADC Power Down
    Uint16 ADCBSYCHN:4;                 // 11:8 ADC Busy Channel
    Uint16 rsvd3:1;                     // 12 Reserved
    Uint16 ADCBSY:1;                    // 13 ADC Busy
    Uint16 rsvd4:2;                     // 15:14 Reserved
};
struct ADCCTL1_BITS bit;   //声明一个变量 bit,他的数据类型是ADCCTL1_BITS
bit.INTPULSEPOS=1;          //就可以通过bit对寄存器里面的每一位进行操作了

以上就是采用位定义的方式来定义寄存器结构的全部过程。

3.声明共同体

位定义实现了对寄存器按照进行操作,但实际上并不是每一次都是对寄存器按照位进行操作的,也有可能是对寄存器整体进行操作,也就是说对寄存器整体进行赋值。

 想要对寄存器既能按进行操作,也能整体进行操作,那就通过声明共同体的方式来实现。

声明共同体的语法格式:

union a_REG(共同体的名称)
{
   Uint16 all;            //可实现对寄存器整体进行操作
   struct a_BITS bit;     //可实现位操作
};
union a_REG a;//上面共同体配置完后就可以申请一个a_REG这个共同体类型的变量a
a.all = 0x0001;//对a整体进行操作
a.bit.(bit里面的位名称) = 1;//对a里面的位进行操作

结构体和共同体的区别:结构体在分配存储器空间时,是按照所有成员占用的总和去分配,比如某个寄存器占用两个字节,那么存储空间就会分配给结构体定义的寄存器两个字节;但是共同体,是共用一个存储空间,比如上面union的成员有两个变量,一个是无符号16位的整形的变量,一个是struct位定义的16位结构体类型的变量,那么在使用union过程中,系统只会分配16位的存储空间,并不是32位的,也就是说这两个变量共用一个16位存储空间,两个变量不会同时使用,在同一个时间只有一个变量在使用,这就是共同体。

前言位定义中的ADC为例,当位定义完成以后,就可以进行声明共同体(F2837xD_adc.h):

4.寄存器结构体文件

以上学习了使用结构体定义了一个寄存器的值,但是各个模块(还是以ADC为例)有很多寄存器,这个时候需要创建一个大的结构体,来包含ADC模块的所有寄存器。

当完成第二步位定义和第三步声明共同体了以后,就可以进行结构体文件的构建:

寄存器结构体文件的语法格式:

struct ADC_REGS
{
union   a_REG    a;
union   b_REG    b;
Uint16  c_REG    c;
...
}
extern volatile struct ADC_REGS ADCaREGS;
extern volatile struct ADC_REGS ADCbREGS;
extern volatile struct ADC_REGS ADCcREGS;
extern volatile struct ADC_REGS ADCdREGS;

struct 把之前所有声明的共同体文件都包含在ADC_REGS里面,定义完成后,在最后要声明ADC_REGS类型的变量ADCaREGS,ADCaREGS这个变量就包含了ADCa所有定义的共同体,ADCbREGS同理;

其中关键字extern 的意思是“外部的”,表明这个变量在外部文件中被调用,是一个全局变量,在整个工程中都可以使用。

关键字 volatile 的意思是“易变的”,使得寄存器的值能够被外部代码任意改变。例如可以被外部硬件或者中断任意改变。如果不使用关键字 volatile,则寄存器的值只能被程序代码所改变。

官方给的F2837xD_adc.h中的定义寄存器结构体文件如下:

注:ADC寄存器结构体中有的成员是union形式的,有的是Uint16形式的。定义为union形式的成员既可以实现对寄存器的整体操作,也可以实现对寄存器进行位操作;而定义为 int16 的成员只能直接对寄存器进行操作。

注:在其寄存器的存储空间中,有的存储单元是被保留的,在对 SCI 的寄存器进行结构体定义时,也要将其保留。保留的寄存器空间采用变量来代替,但是该变量不会被调用,如 rsvd1,rsvd2,rsvd3等等。

5.寄存器文件的空间分配

以上所做的工作只是将 F28379D的寄存器按照C语言中位域定义和寄存器结构体的方式组织了数据结构,当编译时,编译器会把这些变量分配到存储空间中,但是很显然还有一个问题需要解决,就是如何将这些代表寄存器数据的变量同实实在在的物理寄存器结合起来呢?

这个工作需要两步来完成:第1步使用DATA_SECTION的方法将寄存器文件分配到数据空间中的某个数据段;第2步在CMD文件中,将这个数据段直接映射到这个外设寄存器所占的存储空间。通过这两步就可以将寄存器文件同物理寄存器相结合起来了。

5.1使用DATA_SECTION的方法将寄存器文件分配到数据空间

编译器产生可重新定位的数据和代码模块,这些模块就称为段。这些段可以根据不同的系统配置分配到相应的地址空间,各段的具体分配空间方式在 CMD 文件中定义。在采用硬件抽象层设计方法的情况下,变量可以采用“# pragma DATA_SECTION”命令分配到特殊的数据空间。

DATA_SECTION的语法格式为:

# pragma DATA_SECTION(symbol,"section name");

其中,symbol是变量名,而section name是数据段名;

这个是F2837xD_GlobalVariableDefs.c文件中的一段,作用就是将AdcaRegs分配到名字为 AdcaRegsFile 的数据段。然后CMD 文件会将这个数据段直接映射到相应的存储空间里。

前言说明了AdcaRegs寄存器映射到起始地址为0x00007400 的存储空间。使用分配好的数据段,变量 AdcaRegs就会分配到起始地址为0x00007400的存储空间。那如何将数据段映射到寄存器对应的存储空间呢?这得研究一下CMD文件中的内容。

5.2将数据段映射到寄存器对应的存储空间

在F2837xD_Headers_nonBIOS_CPU1.cmd文件中:

MEMORY部分ADCA寄存器的物理地址从0x00007400开始,长度为80(8*16^1个字节);

然后在 SECTIONS部分,数据段 AdcaRegsFile 被映射到了“ADCA”,实现了数据段映射到相应的存储器空间。

通过以上两部分的操作,完成了将外设寄存器的结构体文件映射到寄存器的物理地址空间,这样就可以通过C语言来实现对F28379D寄存器的操作;

6.总结

其实以上内容是文件(F2837xD_adc.h、F2837xD_GlobalVariableDefs.c、F2837xD_GlobalVariableDefs.c)的内容,也是为了慢慢理解DSP各个底层文件的作用来学习这些的东西。

各个模块的这些底层文件都是官方给定义好的,没有特殊情况直接拿来使用就可以,不需要修改。

这就是为什么在写程序时,当把相应寄存器输入出来,后面打个“.”后就会出来这么一堆东西。

7.参考文献

以上学习借鉴了顾卫钢老师的书《手把手教你学DSP——基于TMS320X281x》第三版,上面讲的更清楚,想学习DSP的小伙伴可以参考一下!!!

本文到这就结束了,若上述有问题请多多指教,本人也是刚开始学习DSP,多多讨论有助于我们的共同成长,感谢大家。

  • 21
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,但我无法为您提供完整的代码。不过,我可以给您提供一个大致的代码框架,以帮助您开始编写代码。请注意,这只是一个示例,并且可能需要根据您的具体硬件和软件环境进行适当调整。 ```c #include <stdint.h> #include "F2837xS_device.h" #include "F2837xS_Examples.h" #define SPI_BAUDRATE 1000000 // SPI通信的波特率 void initSPI(void) { // 初始化SPI控制器的设置 // 设置SPI模块的控制寄存器和时钟分频器等参数 // 例如:SpiaRegs.SPICCR.bit.SPISWRESET = 0; // 先禁用SPI // SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; // SPI时钟极性设置 // ... } void initADC(void) { // 初始化ADS131E08芯片的设置 // 设置ADS131E08的寄存器,例如:配置ADC通道、增益、采样速率等 // 例如:发送配置命令到ADS131E08芯片 // ... } uint16_t readADC(void) { uint16_t adcValue = 0; // 使用SPI读取ADS131E08芯片的ADC值 // 例如:发送读取命令到ADS131E08芯片 // 接收ADC数据并将其存储在adcValue变量中 // ... return adcValue; } int main(void) { // 初始化系统和引脚设置等 initSPI(); // 初始化SPI控制器 initADC(); // 初始化ADS131E08芯片 while (1) { uint16_t adcValue = readADC(); // 读取ADC值 // 处理ADC值,例如打印到终端或进行其他操作 // 例如:printf("ADC Value: %d\n", adcValue); // 等待一段时间,然后进行下一次采样 // 例如:延时函数、定时器等 } return 0; } ``` 请注意,上述代码只是一个大致的框架,具体的实现细节需要根据您的硬件和软件环境进行调整。此外,还需要根据相关文档和数据手册查找正确的寄存器和命令来配置SPI和ADS131E08芯片。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值