看门狗例程
经过我修改后的看门狗例程如下:
//两个头文件,第一个头文件与设备有关,第二个头文件与例程有关
#include "DSP2833x_Device.h" // Headerfile Include File
#include "DSP2833x_Examples.h" // Examples Include File
// 声明一个中断函数
interrupt void wakeint_isr(void);
void ServiceDog1(void);
void EnableDog(void);
//声明全局变量
Uint32 WakeCount; //触发看门狗的次数
Uint32 LoopCount; //循环的次数
Uint32 ServiceCount; //喂狗次数
void main(void)
{
//第一步,初始化系统控制
InitSysCtrl();
//第二步,初始化通用IO口
// InitGpio(); // Skipped for this example
//第三步,清除所有中断,初始化中断向量表;
//禁止CPU中断
DINT;
//初始化所有的PIE控制寄存器到默认的状态;默认状态是禁止所有的PIE中断及所有标志位清除
InitPieCtrl();
//禁止CPU中断,清除CPU中断寄存器。
IER = 0x0000;
IFR = 0x0000;
//初始化中断向量表,中断向量就是采用指针指向中断服务程序入口。
//这个函数将会填充整个中断向量表,包括例程中没有用到的中断。
InitPieVectTable();
//关闭保护
EALLOW;
//写入看门狗中断
PieVectTable.WAKEINT = &wakeint_isr;
//打开保护
EDIS;
//第四步,初始外围设备
// InitPeripherals(); // Not required for this example
//第五步,用户自定义功能代码,使能中断
//清空计数数据
WakeCount = 0;
LoopCount = 0;
ServiceCount = 0;
//将看门狗中断信号WDENINT使能,当触发看门狗时,会触发中断
//写入SCSR寄存器以避免清除WDOVERRIDE位
EALLOW;
SysCtrlRegs.SCSR =BIT1; // 0x0002
EDIS;
//使能WAKEINT中断:Group 1 interrupt 8
//使能INT1,让它连接到WAKEINT
PieCtrlRegs.PIECTRL.bit.ENPIE = 1; // Enable the PIE block
PieCtrlRegs.PIEIER1.bit.INTx8 = 1; // Enable PIE Gropu 1 INT8
IER |= M_INT1; // Enable CPU int1
EINT; // Enable Global Interrupts
//喂狗,复位看门狗计数器
ServiceDog1();
//使能看门狗
EnableDog();
//第六步, 死循环
for (;;)
{
LoopCount++;
//通过注释决定是否喂狗
// ServiceDog1();
}
}
//第七步,中断服务程序
interrupt void wakeint_isr(void)
{
WakeCount++;
// Acknowledge this interrupt to get more from group 1
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}
//其他子程序
void EnableDog(void)
{
EALLOW;
SysCtrlRegs.WDCR = 0x0028; // 使能看门狗
EDIS;
}
void ServiceDog1(void)
{
EALLOW;
SysCtrlRegs.WDKEY = 0x0055;
SysCtrlRegs.WDKEY = 0x00AA;
EDIS;
ServiceCount++;
}
//===========================================================================
// No more.
//===========================================================================
该例程基本上和上篇博文中上篇博客(F28335第一篇——看门狗的开断) 的例程一致。功能上只是增加一个记录喂狗次数的标量ServiceCount;
DSP开发的主要步骤
通过官方的例程,可以总结出TI推荐的开发流程大致可以分成7步:
- 初始化系统控制
- 初始化通用IO(GPIO)
- 清除所有的中断,初始化中断向量表
- 初始化外围设备
- 用户自定义代码
- 死循环
- 中断服务程序
当然这七个步骤并不是所有工程都全部需要,在本例程中第2步和第4步就被忽略了,下面重点介绍系统控制初始化。
初始化系统控制
这一步是系统控制初始化,调用了InitSysCtrl()函数。该函数定义在DSP2833x_SysCtrl.c文件中。源代码为:
void InitSysCtrl(void)
{
//禁止看门狗
DisableDog();
//初始化PLL控制,此处确定系统的主时钟
InitPll(DSP28_PLLCR,DSP28_DIVSEL);//InitPll(10,2);先10倍频,在2分频
//初始化外设时钟
InitPeripheralClocks();
}
这一步通过源代码可以发现总共实现了三个功能。
- 禁止看门狗
- 设置系统时钟频率
- 对需要外设时钟进行使能。在这里还设置高低速时钟频率。
1禁止看门狗
void DisableDog(void)
{
EALLOW;
SysCtrlRegs.WDCR= 0x0068;
EDIS;
}
在F28335中,TI公司对一些寄存器设置了EALLOW保护。代码中的EALLOW和EDIS实质上是执行了汇编程序。他们的定义为:
#define EALLOW asm(" EALLOW")
#define EDIS asm(" EDIS")
以下引用网上的解释,我没有全部验证:
F2812中有一些配置寄存器是受保护的,无法直接操作。在对这些寄存器进行修改之前,需要先去掉保护功能。而保护状态是由状态寄存器中EALLOW标志来指示的。汇编指令“EALLOW”就是将该标志位置位,允许对受保护的寄存器操作。
EALLOW(Edit allow)一般和EDIS(Edit disable)配套使用,在对受保护的寄存器操作之后,用EDIS恢复寄存器的被保护状态。
F280x器件上的几个控制寄存器受EALLOW 保护机制保护以防止虚假的CPU 写入。在复位时EALLOW位被清除以启用EALLOW 保护。在受保护时,CPU 对受保护寄存器进行的所有写入被忽略且只允许CPU读取、JTAG读取和JTAG写入。如果设置了此位,则通过执行EALLOW指令可以允许CPU自由写入受保护的寄存器。在修改寄存器之后,可以通过执行EDIS指令清除EALLOW位使它们再次受保护。
关于EALLOW保护的更多内容,可以参见(F28335第五篇——EALLOW和EDIS)
其中与看门狗相关的寄存器,可以参见上篇博客(F28335第一篇——看门狗的开断) 。
2设置系统时钟频率
InitPll函数的源程序为:
void InitPll(Uint16 val, Uint16 divsel)//本系统中val=10,divsel=2
{
// Make sure the PLL is not running in limp mode
//保证PLL没有工作在无序状态
if (SysCtrlRegs.PLLSTS.bit.MCLKSTS != 0)
{
// Missing external clock has been detected
// Replace this line with a call to an appropriate
// SystemShutdown(); function.
//调试时候停止系统运行,实际工程中要自定义应对策略。
asm("ESTOP0");
}
// DIVSEL MUST be 0 before PLLCR can be changed from
// 0x0000. It is set to 0 by an external reset XRSn
// This puts us in 1/4
//先将PLL电路设计成4分频
if (SysCtrlRegs.PLLSTS.bit.DIVSEL != 0)
{
EALLOW;
SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;//保证PLL是4分频
EDIS;
}
// Change the PLLCR
if (SysCtrlRegs.PLLCR.bit.DIV != val)
{
EALLOW;
// Before setting PLLCR turn off missing clock detect logic
SysCtrlRegs.PLLSTS.bit.MCLKOFF = 1;
SysCtrlRegs.PLLCR.bit.DIV = val;
EDIS;
// Optional: Wait for PLL to lock.
// During this time the CPU will switch to OSCCLK/2 until
// the PLL is stable. Once the PLL is stable the CPU will
// switch to the new PLL value.
//
// This time-to-lock is monitored by a PLL lock counter.
//
// Code is not required to sit and wait for the PLL to lock.
// However, if the code does anything that is timing critical,
// and requires the correct clock be locked, then it is best to
// wait until this switching has completed.
// Wait for the PLL lock bit to be set.
// The watchdog should be disabled before this loop, or fed within
// the loop via ServiceDog().
// Uncomment to disable the watchdog
DisableDog();
while(SysCtrlRegs.PLLSTS.bit.PLLLOCKS != 1)
{
// Uncomment to service the watchdog
// ServiceDog();
}
EALLOW;
SysCtrlRegs.PLLSTS.bit.MCLKOFF = 0;
EDIS;
}
// If switching to 1/2
if((divsel == 1)||(divsel == 2))
{
EALLOW;
SysCtrlRegs.PLLSTS.bit.DIVSEL = divsel;
EDIS;
}
// If switching to 1/1
// * First go to 1/2 and let the power settle
// The time required will depend on the system, this is only an example
// * Then switch to 1/1
if(divsel == 3)
{
EALLOW;
SysCtrlRegs.PLLSTS.bit.DIVSEL = 2;
DELAY_US(50L);//时间是根据系统调节
SysCtrlRegs.PLLSTS.bit.DIVSEL = 3;
EDIS;
}
}
个人理解
此处主要涉及到PLL电路的初始化。所谓PLL电路,就是锁相环电路(Phase-Locked Loop)。电路通过比较复杂的原理可以将低频信号进行倍频与分频处理。F28335的工作最高频率为150M Hz,而一般在电路设计中,一般采用的晶振频率为30MHz。之所以不直接采用150MHz晶振,一方面是价格高昂,另一方面还要对电路做EMI处理。因此,为了让DSP能够工作在最高频率,需要对晶振信号频率进行5倍频处理。通常采用的方案是通过PLL电路先进行10倍频处理,再2分频处理。
程序的大致流程为:
- 确保时钟工作在有序状态;
- 先将PLL电路设置为4分频(DIVSEL=0);
- 设置PLL电路的倍频数div;
- 关闭时钟检测电路;
- 写入倍频数div
- 关闭看门狗
- 等待PLL电路锁定(若是没有关闭看门狗,则此步骤需要喂狗)
- 开启时钟检测电路
- 设置分频数divsel;
- 若divsel为1或者2,直接设置对应的分频数
- 若divsel为3,则先设定为2分频,延时一段时间,在设定为1分频
相关寄存器
PLLSTS(锁相环状态寄存器)
位 | 名称 | 描述 |
---|---|---|
15~9 | 保留 | |
8~7 | DIVSEL | 时钟分频选择; 00,01:4分频; 10:2分频;11:1分频 |
6 | MCLKOFF | 时钟检测电路关闭位; 0:默认模式,时钟检测使能; 1:时钟检测电路关闭,系统不会进入limp-mode模式 |
5 | OSCOOF | 振荡器时钟禁止位; 0:晶振时钟信号会被送到PLL电路; 1:晶振时钟信号不会被送到PLL电路。 当此位=1时, 此时不要进入HALT或STANDBY模式,不要写入PLLCR寄存器,否则会倒追不可预知的后果; 看门狗行为与输入时钟有关,若X1或X1与X2,看门狗不工作。XCLKIN:看门狗工作。 OSCOFF用于测试时钟监视逻辑电路。 |
4 | MCLKCLR | 时钟丢失状态清除位 0:写无影响,读取返回0; 1:强制将时钟检测电路进行复位。如果OSCCLK(晶振时钟)依然丢失,时钟检测电路会再次产生一次系统复位信号,并将MCLKSTS置位,此时CPU的工作时钟为limp-mode模式产生的低速时钟 |
3 | MALKSTS | 对该位写无效,写MCLKCLR或者外部复位时候,该位被清除 0:正常模式,时钟信号没有丢失; 1:晶振信号丢失,CPU工作在limp-mode |
2 | PLLOFF | PLL电路关闭位,只有PLLCR=0时候,才能将PLL电路关闭 0:PLL电路正常工作 1:PLL电路关闭 |
1 | 保留 | |
0 | PLLLOCKS | PLL电路状态位 0:表示锁相环依然正在锁相,此时最好等待 1:PLL电路已经锁相完成 |
PLLCR(锁相环控制寄存器)
位 | 名称 | 描述 |
---|---|---|
15~4 | 保留 | |
3~0 | DIV | 0:PLL电路是旁路 其他:DIV为倍频数(最大只能是10倍频,大于10保留) |
3 对需要外设时钟进行使能
void InitPeripheralClocks(void)
{
EALLOW;
// HISPCP/LOSPCP prescale register settings, normally it will be set to default values
SysCtrlRegs.HISPCP.all = 0x0001;//
SysCtrlRegs.LOSPCP.all = 0x0002;//Lowspeedclock=SYSCLKOUT/4=150/4=37.5MHZ;
// XCLKOUT to SYSCLKOUT ratio. By default XCLKOUT = 1/4 SYSCLKOUT
// XTIMCLK = SYSCLKOUT/2
XintfRegs.XINTCNF2.bit.XTIMCLK = 1;
// XCLKOUT = XTIMCLK/2
XintfRegs.XINTCNF2.bit.CLKMODE = 1;
// Enable XCLKOUT
XintfRegs.XINTCNF2.bit.CLKOFF = 0;
// Peripheral clock enables set for the selected peripherals.
// If you are not using a peripheral leave the clock off
// to save on power.
//
// Note: not all peripherals are available on all 2833x derivates.
// Refer to the datasheet for your particular device.
//
// This function is not written to be an example of efficient code.
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1; // ADC
// *IMPORTANT*
// The ADC_cal function, which copies the ADC calibration values from TI reserved
// OTP into the ADCREFSEL and ADCOFFTRIM registers, occurs automatically in the
// Boot ROM. If the boot ROM code is bypassed during the debug process, the
// following function MUST be called for the ADC to function according
// to specification. The clocks to the ADC MUST be enabled before calling this
// function.
// See the device data manual and/or the ADC Reference
// Manual for more information.
ADC_cal();
SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 1; // I2C
SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1; // SCI-A
SysCtrlRegs.PCLKCR0.bit.SCIBENCLK = 1; // SCI-B
SysCtrlRegs.PCLKCR0.bit.SCICENCLK = 1; // SCI-C
SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1; // SPI-A
SysCtrlRegs.PCLKCR0.bit.MCBSPAENCLK = 1; // McBSP-A
SysCtrlRegs.PCLKCR0.bit.MCBSPBENCLK = 1; // McBSP-B
SysCtrlRegs.PCLKCR0.bit.ECANAENCLK=1; // eCAN-A
SysCtrlRegs.PCLKCR0.bit.ECANBENCLK=1; // eCAN-B
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; // Disable TBCLK within the ePWM
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1; // ePWM1
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1; // ePWM2
SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1; // ePWM3
SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1; // ePWM4
SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 1; // ePWM5
SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 1; // ePWM6
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; // Enable TBCLK within the ePWM
SysCtrlRegs.PCLKCR1.bit.ECAP3ENCLK = 1; // eCAP3
SysCtrlRegs.PCLKCR1.bit.ECAP4ENCLK = 1; // eCAP4
SysCtrlRegs.PCLKCR1.bit.ECAP5ENCLK = 1; // eCAP5
SysCtrlRegs.PCLKCR1.bit.ECAP6ENCLK = 1; // eCAP6
SysCtrlRegs.PCLKCR1.bit.ECAP1ENCLK = 1; // eCAP1
SysCtrlRegs.PCLKCR1.bit.ECAP2ENCLK = 1; // eCAP2
SysCtrlRegs.PCLKCR1.bit.EQEP1ENCLK = 1; // eQEP1
SysCtrlRegs.PCLKCR1.bit.EQEP2ENCLK = 1; // eQEP2
SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1; // CPU Timer 0
SysCtrlRegs.PCLKCR3.bit.CPUTIMER1ENCLK = 1; // CPU Timer 1
SysCtrlRegs.PCLKCR3.bit.CPUTIMER2ENCLK = 1; // CPU Timer 2
SysCtrlRegs.PCLKCR3.bit.DMAENCLK = 1; // DMA Clock
SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1; // XTIMCLK
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1; // GPIO input clock
EDIS;
}
以上代码可以大致分成4块内容:
- 设置高速和低速外设时钟的分频HISPCP/LOSCP
- 初始化外部存储器接口
- 初始化AD模块
- 初始化外设时钟控制寄存器
其中,步骤2和步骤3在以后的博客中还会详细介绍。下面重点介绍步骤1和步骤4。
高速和低速外设时钟模块分频设置
高速外设时钟(HISPCP)和低速外设时钟(LOSPCP)寄存器配置方式完全相同,如下:
位 | 名称 | 描述 |
---|---|---|
15~3 | 保留 | |
2~0 | HSPCLK(高速) LSPCLK(低速) | 此三位组成的数设为N,相当于SYSCLKOUT的倍频倍数 如果N=0,不分频;若N不为0,分频数为2N; |
根据上述寄存描述结合源代码:
SysCtrlRegs.HISPCP.all = 0x0001;//
SysCtrlRegs.LOSPCP.all = 0x0002;//Lowspeedclock=SYSCLKOUT/4=150/4=37.5MHZ;
可以发现,高速外设时钟频率是对系统频率进行2分频,即为150/2=75MHz;同理,可以求得低速外设时钟频率为37.5MHz;
初始化外部储存器
源代码为:
// XCLKOUT to SYSCLKOUT ratio. By default XCLKOUT = 1/4 SYSCLKOUT
// XTIMCLK = SYSCLKOUT/2
//设定XTIMCLK的分频数为2,也就是XTIMCLK = SYSCLKOUT/2=150/2=75MHz
XintfRegs.XINTCNF2.bit.XTIMCLK = 1;
// XCLKOUT = XTIMCLK/2
//设定CLKMODE的分频数为2,也就是XCLKOUT = XTIMCLK/2=37.5MHz
XintfRegs.XINTCNF2.bit.CLKMODE = 1;
// Enable XCLKOUT
//打开XCLKOUT信号,使能
XintfRegs.XINTCNF2.bit.CLKOFF = 0;
这部分代码按照本意是设定外部存储器相关的时钟,并且将时钟打开。如上面注释所示。
但是,在实际的工程中,这句话不起作用。这是因为这几个寄存器的设定条件是先打开PCLKR3寄存器。这行代码被写在下面初始话时钟控制寄存器中。所以,这三行代码无论是怎么设定,都对这个系统不产生影响。PCLK3寄存器的设定方法为:
SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1; // XTIMCLK
初始化外设时钟控制寄存器
该部分源代码为:
SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 1; // I2C
SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1; // SCI-A
SysCtrlRegs.PCLKCR0.bit.SCIBENCLK = 1; // SCI-B
SysCtrlRegs.PCLKCR0.bit.SCICENCLK = 1; // SCI-C
SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1; // SPI-A
SysCtrlRegs.PCLKCR0.bit.MCBSPAENCLK = 1; // McBSP-A
SysCtrlRegs.PCLKCR0.bit.MCBSPBENCLK = 1; // McBSP-B
SysCtrlRegs.PCLKCR0.bit.ECANAENCLK=1; // eCAN-A
SysCtrlRegs.PCLKCR0.bit.ECANBENCLK=1; // eCAN-B
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; // Disable TBCLK within the ePWM
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1; // ePWM1
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1; // ePWM2
SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1; // ePWM3
SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1; // ePWM4
SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 1; // ePWM5
SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 1; // ePWM6
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; // Enable TBCLK within the ePWM
SysCtrlRegs.PCLKCR1.bit.ECAP3ENCLK = 1; // eCAP3
SysCtrlRegs.PCLKCR1.bit.ECAP4ENCLK = 1; // eCAP4
SysCtrlRegs.PCLKCR1.bit.ECAP5ENCLK = 1; // eCAP5
SysCtrlRegs.PCLKCR1.bit.ECAP6ENCLK = 1; // eCAP6
SysCtrlRegs.PCLKCR1.bit.ECAP1ENCLK = 1; // eCAP1
SysCtrlRegs.PCLKCR1.bit.ECAP2ENCLK = 1; // eCAP2
SysCtrlRegs.PCLKCR1.bit.EQEP1ENCLK = 1; // eQEP1
SysCtrlRegs.PCLKCR1.bit.EQEP2ENCLK = 1; // eQEP2
SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1; // CPU Timer 0
SysCtrlRegs.PCLKCR3.bit.CPUTIMER1ENCLK = 1; // CPU Timer 1
SysCtrlRegs.PCLKCR3.bit.CPUTIMER2ENCLK = 1; // CPU Timer 2
SysCtrlRegs.PCLKCR3.bit.DMAENCLK = 1; // DMA Clock
SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1; // XTIMCLK
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1; // GPIO input clock
外设时钟控制寄存器有PCKCR0、PCKCR1、PCKCR3这三个。他们每一位的含义在程序全部列出。这部分寄存器比较简单有效位置1,则该外设时钟有效,置0则表示禁止该时钟。在实际应用中为了减少功耗,可以将工程中没有用到的外设时钟关掉。例程中为了调试方便,所有时钟都打开了。