上一课:
【小黑嵌入式系统第十二课】μC/OS-III程序设计基础(二)——系统函数使用场合、时间管理、临界区管理、使用规则、互斥信号量
文章目录
- 1 实验目的
- 2 实验要求
- 3 实验设备
- 4 实验原理
- 4.1 中断
- (1) 中断机制概述
- (2) 中断源
- (3) 中断系统的功能
- 4.2 定时器/计数器
- 5 硬件设计
- 整体硬件仿真图:
- 基本思路:
- 组件设置:
- 6 软件设计
- 6.1 总体设计
- 6.2 详细设计
- 7 测试与分析
- 初始时:
- 中断:
- 8 结论与问题讨论
1 实验目的
- 熟悉PSoC 5LP 中断系统的工作机制;
- 熟悉PSoC 5LP 端口中断单元(PICU)的工作机制,掌握其控制方法;
- 掌握基于中断的应用的基本设计实现方法;
- 学会使用Creator调试器进行程序调试;
- 设计实现一个由独立按键、定时器及字符型LCD构造的基本秒表。
2 实验要求
- 通过应用笔记AN54460,学习并熟悉PSoC 5LP 中断系统的结构及基本控制方法;理解示例项目A,掌握基于中断的定时器控制方法;
- 通过应用笔记AN54460和AN72382,学习并熟悉PSoC 5LP端口中断单元(PICU)的工作机制;理解AN54460中的示例项目B,掌握引脚组件的中断设置及编程控制方法;
- 设计一个秒表项目,实现以下功能
a. 按键SW2为“停止/重新运行”,按键SW3为“暂停/恢复运行”,LCD用作秒表时间显示,秒表的时间分辨率为0.1秒,秒表的计时应精确;
b. 系统复位后,LCD计时显示停止在“0.0”秒;
c. SW2在LCD计时显示停止期间按下时,LCD从“0.0”秒重新开始计时显示;SW2在计时期间按下时,LCD的计时显示停止在当前值;
d. SW3在计时期间按下时,LCD的计时显示停止在当前值,并且LCD显示内容以1Hz的频率闪烁;SW3在LCD计时显示停止期间再次按下时,LCD从当前值开始继续进行计时显示(不闪烁)。 - 设计实现秒表项目时,要求:
a. 按键按下时产生中断,使用定时器在达到TC值时产生中断(或者使用时钟信号作为中断源);
b. 由于针对LCD的操作函数很耗时,并且可能会发生主程序和中断服务程序同时操作LCD的情形,因此不得在中断服务程序中调用LCD操作函数;
c. 应消除按键动作时抖动的影响。
3 实验设备
- CY8CKIT-050实验板
- 安装了PSoC Creator软件的PC机
4 实验原理
4.1 中断
(1) 中断机制概述
问题的提出:
有一类事件,它们一定会发生,但又无法预测什么时候发生,比如说火灾等故障事件、键盘有按键按下、通信接有数据发来等突发事件。此时可以采取以下两种方式进行处理:
- 定时查询方式,定期检测是否有事件发生。这将消耗大量的CPU时间来完成查询工作,从而降低CPU的工作效率。
- 中断方式,当突发事件发生时提出请求,CPU可以暂时停止当前正在处理的程序,转去处理突发事件,如故障处理、按键字符数据的接收、通信接口数据的接收,等处理这些事务的程序执行完毕后,再回到原先程序被打断的地方去继续执行后续程序。
应注意,这个运作流程是由中断系统硬件来实现的,用户程序仅仅是配置中断及编写中断服务程序。中断服务程序并不能由用户调用。中断请求一般允许用户程序发起。
中断包括三个过程:
- 请求过程:中断控制器检测到中断事件发生时,向CPU提出中断请求,同时提供给CPU该中断事件对应的中断矢量 。
- 响应过程:CPU在合适的时候向中断控制器返回响应告知将进入中断处理。 CPU执行完当前指令,保存下一条本应执行的指令的地址(断点)至栈中,转入中断矢量处执行中断服务程序。
- 服务过程:执行请求事件的服务程序。结束时向中断控制器返回响应告知将退出中断,从栈中取出之前保存的指令地址,CPU转到该指令地址处执行(即在原被中断的程序位置之后继续执行)。
中断的基本概念:
- 中断源:引发中断的事件称为中断源。
- 中断断点(地址):CPU转入中断服务程序前,原来程序将被暂停,暂停点为将执行但没被执行的下一条指令的地址。
- 中断服务程序:为中断事件服务的程序段称为中断服务程序。因此由不同中断源引发的中断需要转到各自对应的中断服务程序来执行。
- 中断向量/中断矢量:中断服务程序的入口地址。
- 中断服务程序与子程序区别:子程序由某个指令调用,而跳入子程序模块执行,因此,它的调用是由程序设定的。中断服务程序由某个事件的发生而引发,它是随机和不确定的,由硬件自动控制CPU转入,而不由用户主动调用。
使用中断的优点:
- 同步操作
- 实现实时处理
- 故障处理
(2) 中断源
引起中断的原因,或者能够发出中断申请的来源,称为中断源。
分类:
- 硬件和软件相关的中断源
- 软件错误相关的硬件中断
- 实时时钟
- 为调试程序而设置的中断源
(3) 中断系统的功能
为了实现各种中断请求,中断系统应具有以下功能:
- 实现中断及返回
- 能够实现优先级排队
- 高级中断源能够中断低级的中断处理
4.2 定时器/计数器
- 所有的嵌入式处理器都集成了定时器/计数器模块。
- 系统中至少有一个定时器,用作系统时钟。
- 定时器和计数器都是由带有保存当前值的寄存器和向当前寄存器值加1(或减1)的一个增量输入的加法器逻辑电路组成。或者说是一种能够累计输入脉冲的个数的数字电路。它由触发器构成,具有记忆功能,除了能够完成计数外,还能够用作分频和定时。
下降沿动作的异步二进制加法计数器:
三位加法计数器时序图:
定时器、计数器的区别:
- 定时器的计数装置是连到已知的周期性时钟信号上的,用来测量时间间隔;
- 计数器的计数装置是连到非周期性信号上的,用来计外部事件的发生次数。
因为同样的逻辑电路可以有这两种使用方式,所以该设备经常被称为“定时器/计数器”。
嵌入式处理器上的定时器/计数器具有的作用: - 嵌入式操作系统的任务调度,特别是具有时间片轮转调度功能的操作系统必须使用定时器产生时间片。
- 嵌入式操作系统的软件时钟需要基于硬件定时器产生定时信号。
- 通信电路的波特率发生器。
- 实时时钟电路。
- 集成的片上A/D转换和D/A转换电路。
……
定时器的扩展——脉宽调制(PWM)器:
通过在定时器电路中增加比较器等额外电路,容易构成脉宽调制(PWM)器,用于输出一个频率和占空比可调的矩形波。典型地用于电机调速、LED亮度调节、简易数/模转换等等。
定时器/计数器的扩展——捕获(Capture):
通过在定时器/计数器电路中增加锁存器等额外电路,容易实现捕获功能。此时向定时器提供已知频率的时钟,用户通过设置捕获的(硬件)触发条件,可在触发条件发生时刻将当前计数值锁存到捕获寄存器中。
捕获机制典型地用于硬件事件发生时刻的精确定时。可以避免“事件发生时刻→软件获取到计数值”这段时间的计时不准确性。
5 硬件设计
整体硬件仿真图:
基本思路:
- 定义一个计数器变量,用于记录秒表当前的计时时间,初始值为0。
- 定义一个标志变量,用于记录秒表的状态,包括“运行中”、“暂停中”和“停止”三种状态。
- 定义一个定时器,设置定时器的周期为0.1秒,用于产生中断。在中断服务程序中,更新计数器变量的值,并将计数器的值转换为时分秒的格式,然后将时间显示在LCD上。注意,在中断服务程序中不要直接调用LCD操作函数,而是将需要更新的数据传递给主程序,由主程序在主循环中更新LCD上的显示内容。
- 定义两个外部中断,分别用于处理按键SW2和SW3的按下事件。在外部中断服务程序中,根据按键的状态进行相应的操作。
- 在系统复位时,将计数器变量的值清零,并将标志变量设置为“停止”状态。然后在主循环中更新LCD上的显示内容,将计数器变量的值转换为时分秒的格式,并将时间显示在LCD上。
- 处理按键SW2的按下事件。如果当前秒表状态为“停止”或者“暂停中”,则将计数器变量的值清零,并将标志变量设置为“运行中”状态。如果当前秒表状态为“运行中”,则将标志变量设置为“暂停中”状态。
- 处理按键SW3的按下事件。如果当前秒表状态为“运行中”,则将标志变量设置为“暂停中”状态,并在主循环中将LCD上的显示内容以1Hz的频率闪烁。如果当前秒表状态为“暂停中”,则将标志变量设置为“运行中”状态,并在主循环中将LCD上的显示内容恢复正常。
8.为了消除按键动作时抖动的影响,可以在外部中断服务程序中使用软件延时或者硬件去抖动电路来处理按键的按下事件。例如,在按键按下时,先进行一个短暂的延时,然后再检测按键的状态,如果按键仍然处于按下状态,则认为按键的按下事件有效。
组件设置:
6 软件设计
6.1 总体设计
- 主程序
main
:包含了系统的主要逻辑,根据秒表的状态进行不同的操作,并控制LCD的显示。 - LCD显示函数
lcd_printtime
:用于将秒表计时的数值转换为时分秒的格式,并在LCD上进行显示。 - 定时器中断服务程序
isr_tmr_Interrupt
:处理定时器中断,并根据按键状态更新全局中断标志flag_int
。
接下来,描述一下程序中各模块(函数)的功能、入口参数、返回值、调用与被调用情况: main
函数:主要逻辑控制,不接受参数,不返回数值。调用了lcd_printtime
函数进行LCD显示,同时根据秒表状态和全局中断标志进行逻辑判断。lcd_printtime
函数:将秒表计时的数值转换为时分秒的格式,并在LCD上进行显示。入口参数为行号、列号和计时数值,不返回数值。isr_tmr_Interrupt
函数:处理定时器中断,更新全局中断标志flag_int
。不接受参数,不返回数值。
6.2 详细设计
#include <project.h>
#include <stdio.h>
#define COUNTING 0 //正常计时
#define STOP 1 //停止计时
#define LCDSAN 2 //LCD闪烁
#define INT_NONE 0
#define INT_TIMER_MASK 0x01 //计时标记二进制0001
#define INT_SW2ON_MASK 0x02 //SW2开启标记二进制0010
#define INT_SW3ON_MASK 0x04 //SW3开启标记二进制0100
#define INTERVAL_BLINK 10 // LCD闪烁的100微秒的半周期
#define SW_ON 0
#define SW_OFF 1
unsigned char flag_int = INT_NONE;//全局中断
void lcd_printtime(unsigned char row, unsigned char col, unsigned int value);
extern unsigned char flag_int;//输入标记
/* `#END` */
CY_ISR(isr_tmr_Interrupt)
{
#ifdef isr_tmr_INTERRUPT_INTERRUPT_CALLBACK
isr_tmr_Interrupt_InterruptCallback();
#endif /* isr_tmr_INTERRUPT_INTERRUPT_CALLBACK */
/* Place your Interrupt code here. */
/* `#START isr_tmr_Interrupt` */
static unsigned char sw2_status_prev = SW_OFF, sw3_status_prev = SW_OFF;
unsigned char sw2_status_cur, sw3_status_cur;
Timer_1_ReadStatusRegister();
flag_int |= INT_TIMER_MASK;
sw2_status_cur = SW_2_Read();
if((sw2_status_prev == SW_OFF) && (sw2_status_cur == SW_ON))
{
flag_int |= INT_SW2ON_MASK;
}
sw2_status_prev = sw2_status_cur;
sw3_status_cur = SW_3_Read();
if((sw3_status_prev == SW_OFF) && (sw3_status_cur == SW_ON))
{
flag_int |= INT_SW3ON_MASK;
}
sw3_status_prev = sw3_status_cur;
}
int main(void)
{
unsigned int value=0;
unsigned char stopwatch_status;//状态
unsigned char flag_blink;//LCD闪烁标志
unsigned char tmr_blink;//LCD闪烁时长标志
LCD_Start();
Timer_1_Start();
isr_1_Start();
CyGlobalIntEnable;
LCD_Position(0,0);
LCD_PrintString("StopWatch:");
lcd_printtime(1, 3, value);
stopwatch_status = STOP;
flag_blink = 0;
tmr_blink = 0;
value=0;
isr_1_StartEx(isr_tmr_Interrupt);
for(;;)
{
/* Place your application code here. */
switch(stopwatch_status)
{
case COUNTING://计时状态
if((flag_int & INT_SW2ON_MASK) != 0)//进入STOP状态计时停止
{
stopwatch_status=STOP;
LED4_Write(0);
}
else if((flag_int & INT_SW3ON_MASK) != 0)//进入STOP状态计时停止,LCD闪烁标志位开启
{
stopwatch_status=STOP;
flag_blink = 1;
}
else if((flag_int & INT_TIMER_MASK) != 0)//计时的状态的功能
{
value++;
LED4_Write(1);
LCD_DisplayOn();
lcd_printtime(1, 3, value);
}
else//在定时器中断周期退出
{
break;
}
flag_int = INT_NONE;//清除全局中断标志
break;
case STOP://暂停计时
if((flag_int & INT_SW2ON_MASK) != 0)//SW2按下,计时初始化
{
value=0;
stopwatch_status=COUNTING;
LED3_Write(1);
}
else if((flag_int & INT_SW3ON_MASK) != 0)//SW3按下,恢复计时
{
stopwatch_status=COUNTING;
}
else if(((flag_int & INT_TIMER_MASK) != 0))//STOP状态的功能
{
if(flag_blink==1)//若有闪烁标志则进入LCD闪烁状态
stopwatch_status=LCDSAN;
else
stopwatch_status=STOP;
}
else
{
break;
}
flag_int = INT_NONE;
break;
case LCDSAN://LCD闪烁状态
if((flag_int & INT_SW2ON_MASK) != 0)//SW2按下则初始化计时
{
value=0;
flag_blink = 0;
stopwatch_status=COUNTING;
}
else if((flag_int & INT_SW3ON_MASK) != 0)//SW3退出闪烁,并且恢复计时
{
flag_blink = 0;
stopwatch_status=COUNTING;
}
else if((flag_int & INT_TIMER_MASK) != 0)//闪烁程序
{
if(tmr_blink < INTERVAL_BLINK-1)
{
tmr_blink++;
LCD_DisplayOff();
lcd_printtime(1, 3, value);
}
else /* Another blink interval ends. */
{
tmr_blink = 0;
LCD_DisplayOn();
lcd_printtime(1, 3, value);
}
}
else
{
break;
}
flag_int = INT_NONE;
break;
default:
break;
}
}
}
void lcd_printtime(unsigned char row, unsigned char col, unsigned int value)
{
unsigned char hour, min, sec, millisec;
char disp_str[11];
hour = (value/36000)%24;
min = (value%36000)/600;
sec = (value%600)/10;
millisec = value%10;
sprintf(disp_str, "%02u:%02u:%02u.%1u", hour, min, sec, millisec);
LCD_Position(row,col);
LCD_PrintString(disp_str);
}
7 测试与分析
初始时:
中断:
8 结论与问题讨论
- 定时器精度问题:PSoC 5LP的定时器可能存在精度问题,导致秒表计时不准确。可以通过对定时器进行精确的配置和校准,使用更高精度的定时器模块,或者使用外部时钟源来提高定时器的精度。
- 中断处理问题:秒表功能需要使用定时器中断来实现计时,可能会遇到中断处理不及时或者中断嵌套的问题。需要确保中断处理程序的执行时间尽可能短,并避免中断嵌套。可以通过合理的中断优先级设置和中断嵌套的管理来解决中断处理问题。