再上一篇:20.5 使用调试器
上一篇:20.6 指令模拟器
主页
下一篇:附录A
再下一篇:附录B
文章列表

20.8 使用中断实现的秒表示例程序

《Cortex-M3 权威指南》,嵌入式处理器开发教程。

终于到了最后一节了,来看压轴好戏吧! 在这一小节中,将给出了一个最完整的示例——秒表程序示例。它使用了SysTick异常和UART0
中断。秒表程序内部是以状态机的方式实现的,其状态转换图如下:

在上例的基础上,我们使用PC来通过UART以控制秒表程序的执行。为简化示例代码,我们使用 固定的50MHz主频。时间测量上,由SysTick提供时基——它以100Hz的频率给出异常请求。本例中, SysTick以内核的50MHz时钟运行(FCLK),每次响应SysTick中断时,如果秒表在走,则把计数器 加1——自增TickCounter变量。
因为使用UART显示文字是个很耗费时间的工作,因此不再使用以前的查询方式,而转用中断来 实现(这也是编程基本功),而对于秒表数值的格式化则在mani()中完成(线程模式下)。程序中的 主状态机由UART服务例程启动状态转换——每收到一个字符转换一次。
使用上例的创建步骤,我们再创建一个名为stopwatch的工程。这次添加的代码是

stopwatch.c:

#include "stdio.h"

#define CR 0x0D // Carriage return

#define LF 0x0A // Linefeed void Uart0Init(void);

void SysTickInit(void); void SetClockFreq(void); void DisplayTime(void);

void PrintValue(int value);

int sendchar(int ch);

int getkey(void);

void Uart0Handler(void);

void SysTickHandler(void);

// 寄存器地址

#define SYSCTRL_RCC ((volatile unsigned long *)(0x400FE060))

#define SYSCTRL_RIS ((volatile unsigned long *)(0x400FE050))

#define SYSCTRL_RCGC1 ((volatile unsigned long *)(0x400FE104))

#define SYSCTRL_RCGC2 ((volatile unsigned long *)(0x400FE108))

#define GPIOPA_AFSEL ((volatile unsigned long *)(0x40004420))

#define UART0_DATA ((volatile unsigned long *)(0x4000C000))

#define UART0_FLAG ((volatile unsigned long *)(0x4000C018))

#define UART0_IBRD ((volatile unsigned long *)(0x4000C024))

#define UART0_FBRD ((volatile unsigned long *)(0x4000C028))

#define UART0_LCRH ((volatile unsigned long *)(0x4000C02C))

#define UART0_CTRL ((volatile unsigned long *)(0x4000C030))

#define UART0_IM ((volatile unsigned long *)(0x4000C038))

#define UART0_RIS ((volatile unsigned long *)(0x4000C03C))

#define UART0_ICR ((volatile unsigned long *)(0x4000C044))

#define NVIC_IRQ_EN0 ((volatile unsigned long *)(0xE000E100))

// 全局变量

volatile int CurrState; // 状态机

volatile unsigned long TickCounter; // 秒表当前值 volatile int KeyReceived; // 指示用户按下了键 volatile int userinput ; // 用户按下的键

#define IDLE_STATE 0 // 状态的定义

#define RUN_STATE 1

#define STOP_STATE 2 int main (void)

{

int CurrStateLocal; // 局部变量

// 初始化全局变量

CurrState = 0; KeyReceived = 0;

// 初始化硬件

SetClockFreq(); // 设置时钟

Uart0Init(); SysTickInit();

printf ("Stop Watch\n");

while (1)

{

CurrStateLocal = CurrState; // 建立一个局部的复本

// 因为SysTick ISR随时可能修改它的值

switch (CurrStateLocal) {

case (IDLE_STATE):

printf ("\nPress any key to start\n");

break;

case (RUN_STATE):

printf ("\nPress any key to stop\n");

break;

case (STOP_STATE):

printf ("\nPress any key to clear\n");

break;

default:

CurrState = IDLE_STATE;

break;

} // end of switch

while (KeyReceived == 0)

{

if (CurrState==RUN_STATE)

{

DisplayTime();

}

}; // 等待用户输入

if (CurrStateLocal==STOP_STATE)

{

TickCounter=0;

DisplayTime(); //显示,以指示结果被清

}

else if (CurrStateLocal==RUN_STATE)

{

DisplayTime(); // 显示结果

}

if (KeyReceived!=0) KeyReceived=0;

}; // end of while loop

} // end of main

void SetClockFreq(void)

{

// Set BYPASS, clear USRSYSDIV and SYSDIV

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xF83FFFFF) | 0x800 ;

// Clr OSCSRC, PWRDN and OEN

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xFFFFCFCF);

// 修改 SYSDIV, 设置 USRSYSDIV 和 Crystal 的值

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xF87FFC3F) | 0x01C002C0;

// 等待直到PLLRIS置位

while ((*SYSCTRL_RIS & 0x40)==0); // 等待直到PLLLRIS 置位

// 清除bypass

*SYSCTRL_RCC = (*SYSCTRL_RCC & 0xFFFFF7FF) ;

return;

}

// UART0 初始化

void Uart0Init(void)

{

*SYSCTRL_RCGC1 = *SYSCTRL_RCGC1 | 0x0003; // 使能 UART0 & UART1 时钟

*SYSCTRL_RCGC2 = *SYSCTRL_RCGC2 | 0x0001; // 使能 PORTA 时钟

*UART0_CTRL = 0; // 除能 UART

*UART0_IBRD = 27; // 基于50MHz编程波特率

*UART0_FBRD = 9;

*UART0_LCRH = 0x60; // 8 bit, 无奇偶

*UART0_CTRL = 0x301; // 使能 TX 和 RX, 并使能 UART

*UART0_IM = 0x10; // 使能 UART 接收中断

*GPIOPA_AFSEL = *GPIOPA_AFSEL | 0x3; // 让UART0控制GPIO管脚

*NVIC_IRQ_EN0 = (0x1<<5); // 在NVIC中使能UART中断

return;

}

// SYSTICK 初始化

void SysTickInit(void)

{

#define NVIC_STCSR ((volatile unsigned long *)(0xE000E010))

#define NVIC_RELOAD ((volatile unsigned long *)(0xE000E014))

#define NVIC_CURRVAL ((volatile unsigned long *)(0xE000E018))

#define NVIC_CALVAL ((volatile unsigned long *)(0xE000E01C))

*NVIC_STCSR = 0; // 除能 SYSTICK

*NVIC_RELOAD = 499999; // 基于50MHz主频的100Hz装载值

*NVIC_CURRVAL = 0; // 清除当前值

*NVIC_STCSR = 0x7; // 使能SYSTICK,使能中断,使用内核时钟

return;

}

// SYSTICK 异常服务例程

void SysTickHandler(void)

{

if (CurrState==RUN_STATE) { TickCounter++;

}

return;

}

// UART0 RX 中断服务例程

void Uart0Handler(void)

{

userinput = getkey();

// 表示收到了按键请求

KeyReceived++;

// 释放UART请求

*UART0_ICR = 0x10;

// 状态机转换

switch (CurrState)

{

case (IDLE_STATE): CurrState = RUN_STATE;

break;

case (RUN_STATE):

CurrState = STOP_STATE;

break;

case (STOP_STATE):

CurrState = IDLE_STATE;

break;

default:

CurrState = IDLE_STATE;

break;

} // end of switch return;

}

// 显示时间值

void DisplayTime(void)

{

unsigned long TickCounterCopy; unsigned long TmpValue; sendchar(CR);

TickCounterCopy = TickCounter; // 建立一个局部的复本

// 因为SysTick ISR随时可能修改它的值

TmpValue = TickCounterCopy / 6000; // 分钟

PrintValue(TmpValue);

TickCounterCopy = TickCounterCopy - (TmpValue * 6000); TmpValue = TickCounterCopy / 100; // 秒

sendchar(':'); PrintValue(TmpValue);

TmpValue = TickCounterCopy - (TmpValue * 100);

sendchar(':'); PrintValue(TmpValue); // 1/100秒 sendchar(' ');

sendchar(' ');

return;

}

// 显示10进制数值

void PrintValue(int value)

{

printf ("%d", value);

return;

}

// 往UART0送出一个字符(使用printf来输出数据)

int sendchar (int ch)

{

if (ch == '\n')

{

while ((*UART0_FLAG & 0x20)); // 如果TXFIFO满则等待

*UART0_DATA = CR; // 输出附加的CR,以在超级终端上得到正确的显示

}

while ((*UART0_FLAG & 0x20)); // 如果TXFIFO满则等待

return (*UART0_DATA = ch); // 输出数据

}

// 获取用户输入

int getkey (void)

{

// 从串口读取字节

while (*UART0_FLAG & 0x10); // 如果Rx FIFO空则等待

return (*UART0_DATA);

}

// retarget输出

int fputc(int ch, FILE *f)

{

return (sendchar(ch));

}

为使用中断,本例中的UART初始化代码略有改动。在使用中断前,既要设置UART中断掩蔽寄存
器,又要设置NVIC来打开对应的外中断。对于SysTick,因为它是NVIC内建的,每个CM3芯片都一 样,所以初始化代码也是通用的。
此外,还添加了若干个函数,包括UART和SysTick服务例程、显示函数、以及SysTick初始化 的函数。根据外设的不同设计,中断服务例程可能要手工清除中断标志位,也可能由硬件清零。在 本例中,是通过UART0_ICR来手工清除的。
为了让startup.s能认出我们新添加的两个中断服务例程,需要如下修改startup.s

注意IMPORT指示字的使用。它们后面跟着的函数名是由其它源文件实现的函数。有了它,汇编 器就知道了这个情况,从而相应地处理。
本程序的一次运行情况(亦称为一个实例)如下图所示:

注意:如果使用了虚拟的COM口,则有可能无法正常使用(因为此时按键无法送至目标板),这
是虚拟COM口驱动程序的一个bug导致的。如果遇到这种情况,可能要在另一台没有安装过RVMDK的

PC上测试这个示例程序。


以下内容由译者添加,对学习第4章很有用。 也可以使用和添加C源文件相同的方式,来添加汇编源文件。只不过汇编源文件的
扩展名是.s。下面给也出一个汇编源文件的示例:
这里练习了mov和movt指令(还刻意演示了push/pop),为在C程序中调 用”MovMovTTest”,需要先在某个.h或在使用该函数的.c文件中长明该函数:

void MovMovTTest(void);

如果写一个接受参数的函数,方法类似,但是要使用ARM的调用标准,如:有下面 的汇编函数:

Add3

add r0, r0, r1 add r0, r0, r2 bx lr

则对应的C声明为:

int Add3(int a, int b, int c); //计算a+b+c

这种例子虽然看起来很低等,但是只是为了抛砖引玉。读者可以用上面例子所演示 的骨架,去练习第4章的各种指令;也可以试着把本书中的汇编子程序包装成可以由C调 用的函数。