让一个LED灯闪耀不过瘾,咱们应该让这块开发板完结一点更高难度的使命:比方让两个LED灯闪耀。
……
当然了,以咱们的现在运用的空循环技能,仍是能够完成这点的。可是这样显得略为低端。所以咱们运用一个高端点的技能:中止。还有便是会介绍一下在CMSIS里怎样运用中止。
一、电路
二、完成思路
第一个LED的闪耀仍是用之前运用的空循环吧,别把国际弄得太杂乱了。
第二个LED的闪耀就略微自动化一点了:运用一个定时器,让它在到了需求切换引脚电平的时分告诉咱们一下。这样做的优点便是咱们只需在定时器告诉时重视第二个LED灯,而在其他的时分就能够忙其他事了。(比方让第一个LED闪耀。)
运用的中止源仍是之前用到的RTT。RTT能够在计数器到达特定值时产生中止,这个特定的值(Alarm Value)能够经过拜访RTT报警寄存器(RTT_AR)设定。然后在RTT的中止处理函数中切换LED引脚的电平,一起设定好下一次中止的条件就好了。
三、中止
在中止时,处理器会依据中止号在中止向量表查询中止服务函数(ISR)相关的信息。为此,咱们需求知道RTT的中端号(3),还有中止向量表的方位,然后修正中止向量表。体系控制块(SCB)中有个“向量表偏移寄存器”(SCB_VTOR),在这个地址指向的区域里贮存着一系列的向量,包含外部中止向量表。然后咱们需求知道ISR相关信息在这个向量表的方位。接着修正中止向量表时需求知道它贮存的只要ISR的地址,仍是直接跳转至ISR的指令……(先别忙着着手)
四、main函数之前产生的事
实际上,进口点——即整个程序开端运转的进口,并不是main函数。这个进口是链接器指定的,默许情况下是_start函数。而在Atmel Studio生成的项目中,默许情况下链接器的参数有“–entry=Reset_Handler”的这么一项,意思便是指定程序进口为Reset_Handler。
这个函数的完成在以下文件中:
src\ASF\sam\utils\cmsis\sam4e\source\templates\gcc\startup_sam4e.c
这个是函数也是重置时的中止处理函数。在这个函数中,进行了一系列的初始化作业,其间包含中止向量表的装备。然后在初始化C库之后,就调用main函数了。最终在main函数回来后履行一个死循环。
五、界说中止处理函数
CMSIS现已为界说好了各种ISR的函数原型,一起做好了默许的函数完成。这些函数在以下文件中完成:
src\ASF\sam\utils\cmsis\sam4e\source\templates\exceptions.c
不过默许的函数完成是“弱界说”为Dummy_Handler的别号,这个函数的完成仅仅一个简略的死循环。弱界说意味着咱们能够很方便地在链接时掩盖默许的完成。办法便是从头界说一个具有相同签名的函数。由于默许情况下是“强界说”的,所以就会掩盖掉默许的完成。
六、准备作业
现在程序现已略为杂乱了,需求做些准备作业。
宏界说:
/* LED 运用的GPIO引脚 */
#define LED0_GPIO PIO_PA0
#define LED1_GPIO PIO_PD20
/* LED 闪耀的周期 */
#define LED0_OFF_MS 500
#define LED0_ON_MS 1000
#define LED1_OFF_MS 500
#define LED1_ON_MS 200
辅佐函数CalcRTTNeedInc。之前为了核算经过指定时刻后RTT记数器添加的值,写了几行代码。由于有多个当地要用到这个核算,所以把它笼统出来了:
inline uint32_t CalcRTTNeedInc(unsigned int ms)
{
/* 计数器加一的频率 */
const uint32_t freq = CHIP_FREQ_SLCK_RC / PRESCALE;
/* 核算推迟后,计数器需求添加的值
* need_inc = ms /1000 / (1/freq) */
return (ms * freq / 1000);
}
六、RTT的中止处理
在理论上,本程序在RTT中止时切换第二个LED的引脚电平,并设置下一次中止的条件。
在文件sam4e16e.h中,现已界说好了RTT中止处理函数的原型了,只需完成即可。
需求留意的是,在中止处理函数中,需求经过读取一次RTT_SR以铲除RTT的Alarm情况,不然该中止一向会被触发。
void RTT_Handler(void)
{
/* 经过读取情况寄存器铲除Alarm */
uint32_t _ = RTT->RTT_SR;
uint32_t begin_rttv = ReadRTT_CRTV();
uint32_t int_gap_ms ;
uint32_t need_inc;
if ((PIOD->PIO_ODSR & LED1_GPIO) == 0)
{
/* 现在引脚电平为低,LED是亮的 */
/* 灭灯 */
PIOD->PIO_SODR = LED1_GPIO;
/* 设置下次中止唤醒距离的时刻 */
int_gap_ms = LED1_OFF_MS;
}
else
{
/* 现在引脚电平为高,LED是灭的 */
/* 亮灯 */
PIOD->PIO_CODR = LED1_GPIO;
/* 设置下次中止唤醒距离的时刻 */
int_gap_ms = LED1_ON_MS;
}
/* 核算并设置下一次中止的条件 */
need_inc = CalcRTTNeedInc(int_gap_ms);
RTT->RTT_AR = RTT_AR_ALMV(begin_rttv + need_inc – 1);
return;
}
七、RTT初始化中止启用
假如需求启用中止,需求装备NVIC_ISERx寄存器,并且需求进行必定的核算。而CMSIS也做了相应的作业:
/* 启用中止 */
NVIC_ClearPendingIRQ(RTT_IRQn);
NVIC_EnableIRQ(RTT_IRQn);
关于RTT,装备时只需使能中止,一起设置第一次中止的条件即可。
/* 初始化 RTT */
RTT->RTT_MR = RTT_MR_RTPRES(PRESCALE)
| RTT_MR_RTTRST
| RTT_MR_ALMIEN
;
/* 核算第一次中止的时刻
* 现在灯是亮的,第一次中止即在需求灯灭时
*/
RTT->RTT_AR = RTT_AR_ALMV(
ReadRTT_CRTV() + CalcRTTNeedInc(LED1_ON_MS) -1);
八、禁用看门狗
程序在运转若干秒之后,可能会看到一些不和谐的情况,比方某个LED灯不依照咱们的想象快速闪烁一两下。这是由于看门狗默许是敞开的,而咱们却从来没有“喂狗”,然后导致体系重置。现在我只需禁用看门狗即可:
WDT->WDT_MR = WDT_MR_WDDIS;
PS,完好程序代码:
这一部分完好代码放在下面,今后大约不会再在这个基础上修正了吧。
+ View Code