此内容由EEWORLD论坛网友tiankai001原创,如需转载或用于商业用途需征得作者同意并注明出处
一、任务要求利用MSP430F247单片机的P1和P4端口控制16个发光二极管D1~D16,发光二极管有8种花样显示,显示速度可调,由P2端口的三个按键控制,分别是模式按键、加速按键、减速按键。模式按键按下一次,花样显示模式变换一次,按下8次后循环到第一种模式,加速和减速按键可以控制LED的闪烁速度。
二、分析说明按照任务要求,16个发光二极管可以组成更加丰富的花样变换,由于MSP430单片机的每个端口是8位,因此需要两个完整的端口来控制发光二极管,这还需要在程序中将16位的输出转换为两个8位的输出,此外,本实例还将介绍程序的模块化设计、函数指针数组等内容。
三、电路设计16个花样灯控制的硬件电路如下图所示。值得注意的是,P1和P4端口在连接到LED时,是用的网络标号Q1~Q16来表示网络连接关系,这样就简化了图纸的连接,否则会导致图纸上连线过多过密,影响到图纸的简洁和美观。
四、程序代码由于16个花样灯的功能较为复杂,将所有程序代码写在一个文件中不是一个好的程序设计习惯,此时一般采用模块化编程。
所谓模块化编程是指将一个较大的程序划分为若干功能独立的模块,对各模块进行独立开发,然后再将这些模块统一合并为一个完整的程序。这种方法是C语言中的面向过程的编程方法,可以缩短开发周期,提高程序的可读性和可维护性。
在单片机程序里,程序比较小或者功能比较简单的时候,我们不需要采用模块化编程,但是,当程序功能复杂、涉及的资源较多的时候,模块化编程就能体现它的优越性了。如前面写过的闪烁灯程序、流水灯程序和花样灯程序,每一个程序都是只用一个源文件编写就能完成,但程序较为复杂,涉及的功能比较多,将程序全部集中在一个源文件里,将导致主体程序臃肿且杂乱。这样做降低了程序的可读性、可维护性和代码的重用率。如果把这三个程序当做三个独立的模块放到主体程序中进行模块化编程,效果就不一样了。
实际上,模块化编程就是模块合并的过程,也是建立每个模块的头文件和源文件并将其加入到主体程序的过程。主体程序调用模块的函数是通过包含模块的头文件来实现的,模块的头文件和源文件是模块密不可分的的两个部分,缺一不可。所以,模块编编程必须提供每个模块的头文件和源文件。
下面以花样灯为例来介绍模块化编程。首先将16为花样灯的程序分解为三部分,分别是主函数、按键程序和LED显示程序。
1、建立模块的源文件
在模块化编程里,模块的源文件是实现该模块功能的变量定义和函数定义,不能定义main函数。建立模块源文件的方法很多,直接在主体程序里新建一个文件,把代码添加进去,保存为.c的文件,然后将该文件加入到主体程序;或者是在程序外建立一个记事本文件,把代码添加进去,保存为.c文件,然后把文件添加到主体程序里,在本实例中,把键盘处理代码放在key.c文件中。
程序如下。
#include "msp430f247.h"
#include "stdlib.h"
#include "string.h"
#include "LedDisplay.h"
#include "key.h"
extern unsigned char RunMode;
extern unsigned char LEDDirection;
extern unsigned int SystemSpeed,SystemSpeedIndex;
extern unsigned char LEDFlag;
extern unsigned int LEDIndex;
unsigned int SpeedCode[]={1,2,3,5,8,10,14,17,20,30,
40,50,60,70,80,90,100,120,140,160,
180,200,300,400,500,600,700,800,900,1000};
unsigned char GetKey(void)
{
unsigned char KeyTemp;
unsigned char Key=0;
if((P2IN&0x07) != 0x07)
{
delayms(20);
if((P2IN&0x07) == 0x07)
return 0x00;
KeyTemp=P2IN&0x07;
if(KeyTemp == 0x06)
Key=0x01;
if(KeyTemp == 0x05)
Key=0x02;
if(KeyTemp == 0x03)
Key=0x03;
}
return Key;
}
void SetSpeed(unsigned char Speed)
{
SystemSpeed = SpeedCode[Speed];
}
void process_key(unsigned char Key)
{
if(Key&0x01)
{
LEDDirection=1;
LEDIndex=0;
LEDFlag=1;
RunMode=(RunMode+1)%8;
}
if(Key&0x02)
{
if(SystemSpeedIndex>0)
{
--SystemSpeedIndex;
SetSpeed(SystemSpeedIndex);
}
}
if(Key&0x03)
{
if(SystemSpeedIndex<28)
{
++SystemSpeedIndex;
SetSpeed(SystemSpeedIndex);
}
}
}
2、建立模块的头文件
模块的头文件就是模块和主体程序的接口,里面是模块源文件的函数声明。建立头文件的方法和建立源文件的方法类似,只是在保存的时候,把文件保存为.h格式的文件。本实例中,把键盘处理的源文件里的函数声明放到文件里并保存为key.h。每个模块的头文件最终都要被包含在主体程序里,而且不能重复包含,否则编译器报错。
程序如下。
/*****************************************软件延时,主频1M*******************/
#define CPU_F1 ((double)1000000)
#define delay_us1M(x) __delay_cycles((long)(CPU_F1*(double)x/1000000.0))
#define delay_ms1M(x) __delay_cycles((long)(CPU_F1*(double)x/1000.0))
/****************************************************************************/
unsigned char GetKey(void);
void SetSpeed(unsigned char Speed);
void process_key(unsigned char key);
3、同理,建立LED显示控制的头文件如下
void Mode_0(void);
void Mode_1(void);
void Mode_2(void);
void Mode_3(void);
void Mode_4(void);
void Mode_5(void);
void Mode_6(void);
void Mode_7(void);
void Mode_8(void);
void LedShow(unsigned int LedState);
void delayms(unsigned int t);
4、建立LED显示控制的C文件如下
#include "msp430f247.h"
#include "stdlib.h"
#include "string.h"
#include "LedDisplay.h"
#include "key.h"
extern unsigned char RunMode;
extern unsigned char LEDDirection;
extern unsigned char LEDFlag;
extern unsigned int LEDIndex;
void (*run[8])(void)={Mode_0,Mode_1,Mode_2,Mode_3,
Mode_4,Mode_5,Mode_6,Mode_7};
void Mode_0(void)
{
LedShow(0x0001<
LEDIndex=(LEDIndex+1)%16;
}
void Mode_1(void)
{
LedShow(0x8000>>LEDIndex);
LEDIndex=(LEDIndex+1)%16;
}
void Mode_2(void)
{
if(LEDDirection) LedShow(0x0001<
else LedShow(0x8000>>LEDIndex);
if(LEDIndex==15) LEDDirection =! LEDDirection;
LEDIndex=(LEDIndex+1)%16;
}
void Mode_3(void)
{
if(LEDDirection)
LedShow(~(0x0001<
else
LedShow(~(0x8000>>LEDIndex));
if(LEDIndex==15)
LEDDirection =! LEDDirection;
LEDIndex=(LEDIndex+1)%16;
}
void Mode_4(void)
{
if(LEDDirection)
{
if(LEDFlag)
LedShow(0xfffe<
else
LedShow(~(0x7fff>>LEDIndex));
}
else
{
if(LEDFlag)
LedShow(0x7fff>>LEDIndex);
else
LedShow(~(0xfffe<
}
if(LEDIndex==15)
{
LEDDirection =! LEDDirection;
if(LEDDirection) LEDFlag =! LEDFlag;
}
LEDIndex=(LEDIndex+1)%16;
}
void Mode_5(void)
{
if(LEDDirection)
LedShow(0x000f<
else
LedShow(0xf000>>LEDIndex);
if(LEDIndex==15)
LEDDirection =! LEDDirection;
LEDIndex=(LEDIndex+1)%16;
}
void Mode_6(void)
{
if(LEDDirection)
LedShow(~(0x000f<
else
LedShow(~(0xf000>>LEDIndex));
if(LEDIndex==15)
LEDDirection =! LEDDirection;
LEDIndex=(LEDIndex+1)%16;
}
void Mode_7(void)
{
if(LEDDirection)
LedShow(0x003f<
else
LedShow(0xfc00>>LEDIndex);
if(LEDIndex==15)
LEDDirection =! LEDDirection;
LEDIndex=(LEDIndex+1)%16;
}
void Mode_8(void)
{
LedShow(++LEDIndex);
}
void LedShow(unsigned int LedState)
{
P1OUT=~(LedState&0x00ff);
P4OUT=~((LedState>>8)&0x00ff);
}
void delayms(unsigned int t)
{
unsigned int i;
while(t--)
{
for(i=1000;i>0;i--);
if((P2IN&0x07) != 0x07) break;
}
}
5、在主程序里包含模块的头文件,完成程序总体设计,把上面的key.h和LedDisplay.h分别添加到主程序头部,如下图所示,
主程序如下
五、程序说明从主程序看,代码非常简洁,便与编程人员理解和修改,更重要的是键盘处理程序和显示控制程序可以重复适用于其它设计中。
特别要指出的是在显示控制程序中有一行代码。
void (*run[8])(void)={Mode_0,Mode_1,Mode_2,Mode_3,
Mode_4,Mode_5,Mode_6,Mode_7};
这些代码定义了一个函数指针数组,将8个显示模式的函数地址保存在run数组中,该数组保存的是函数的指针,在主程序中用 run[RunMode]();进入不同的显示模式。RunMode为显示模式变量,可以通过模式按键修改
六、仿真结果与分析装载程序后,即可点击运行按钮观察程序运行结果,并根据运行结果判断程序是否达到设计初衷。