前言
本实验用于学习CC2530芯片GPIO 的配置方法,Led 驱动电路及开关 Led 的原理,按键的使用,实现简单的人机交互。
一、实验相关电路图
由于发光二级管单向导电性,即只有在正向电压(二极管的正极接正,负极接负)下才能导通发光。所以 P1.0、P1.1、P1.4 引脚输出低电平 LED 亮,引脚输出亮电平 LED 熄灭,当 引脚(P0_1 )为低电平时说明按键被按下,高电平时为抬起状态。
TI官方的开发板是高电平点亮LED,但实验板子是低电平亮,更符合国人习惯。
二、实验相关寄存器
由于P0、P1配置方法相同,所以只列出P1寄存器的相关信息:
寄存器 |
|
|
---|---|---|
P1 (0x90) | 端口 1 | 端口 1。通用 I / O 端口。可以从 SFR 位寻址。 |
P1SEL(0xF4) | 端口 1 功能选择 | P1.7 到 P1.0 的功能选择 0: 通用 I / O 1: 外设功能 |
P1DIR(0xFE) | 端口 1 方向 | P1.7 到 P1.0 的 I/O 方向 0: 输入 1: 输出 |
P1INP(0xF6) | 端口 1 输入模式 | P1.7 到 P1.2 的 I/O 输入模式。由于 P1.0 和 P1.1 没有上拉/下拉功能,,P1INP 暂时不需要配置,了解一下为后面的实验打下基础 0: 上拉/下拉(见 P2INP (0xF7)–端口 2 输入模式) 1: 三态 |
按照表格寄存器的内容,对 P1.0、P1.1、P1.4 口进行配置,简化配置指令如下:
P1DIR |= 0x13; // P1.0、P1.1、P1.4 定义为输出,只改变最低位的值而不影响其他位
嵌入式中位运算只修改要修改的位,不要影响到其他位,否则在基础实验中功能单一,感觉不出来,如果在协议栈中就有严重问题了。
P1DIR赋值的对应关系:P1DIR(P1 Direction)是P1口方向控制寄存器,给它赋值就是改变P1对应位的状态
P1DIR(P0DIR相同):设置各个I/O口方向,0为输入,1为输出
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
---|---|---|---|---|---|---|---|
P1.7方向 | P1.6方向 | P1.5方向 | P1.4方向 | P1.3方向 | P1.2方向 | P1.1方向 | P1.0方向 |
举例:P1DIR |= 0x13;转换成二进制为 0001 0011 | |||||||
0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 |
按键 S1 配置如下:
P0SEL &= ~0x02; //设置 P0.1 为普通 IO 口
P0DIR &= ~0x02; //按键接在 P0.1 口上,设 P0.1 为输入模式
P0INP &= ~0x02; //打开 P0.1 上拉电阻
三、源码分析
/****************************************************************************
* 文 件 名: main.c
* 描 述: 按下按键S1控制LED1.LED2.LED3实现跑马灯效果
****************************************************************************/
#include <ioCC2530.h>
typedef unsigned char uchar;
typedef unsigned int uint;
#define LED1 P1_0 // 定义P1.0口为LED1控制端
#define LED2 P1_1 // 定义P1.1口为LED2控制端
#define LED3 P1_4 // 定义P1.4口为LED3控制端
#define KEY1 P0_1 // 定义P0.1口为S1控制端
#define ON 0
#define OFF 1
//以毫秒为单位延时,系统时钟不配置时默认为16M(用示波器测量相当精确)
//入口参数: msec 延时参数,值越大,延时越久
void DelayMS(uint msec)
{
uint i,j;
for (i=0; i<msec; i++)
for (j=0; j<535; j++);
}
//点亮或熄灭所有LED灯:mode为0时LED灯亮 mode为1时LED灯灭
void LedOnOrOff(uchar mode)
{
LED1 = mode;
LED2 = mode;
LED3 = mode; //由于P1.4与仿真器共用,必须拔掉仿真器的插头才能看到LED3的变化
}
// 设置LED相应的IO口
void InitLed(void)
{
P1DIR |= 0x13; // P1.0、P1.1、P1.4定义为输出
LedOnOrOff(1); // 使所有LED灯默认为熄灭状态
}
//设置按键相应的IO口
void InitKey(void)
{
P0SEL &= ~0x02; //设置P0.1为普通IO口
P0DIR &= ~0x02; //按键接在P0.1口上,设P0.1为输入模式
P0INP &= ~0x02; //打开P0.1上拉电阻
}
//读取按键状态,0为抬起 1为按键按下
uchar KeyScan(void)
{
if (KEY1 == 0)
{
DelayMS(10); //延时10MS去抖
if (KEY1 == 0)
{
while(!KEY1); //松手检测
return 1; //有按键按下
}
}
return 0; //无按键按下
}
//程序入口函数
void main(void)
{
InitLed(); //设置LED灯相应的IO口
InitKey(); //设置按键S1相应的IO口
while(1)
{
DelayMS(2);
if (KeyScan()) //扫描按键当前状态,按下时执行跑马灯效果
{
LED1 = ON; //点亮LED1
DelayMS(500);
LED1 = OFF; //熄灭LED1
LED2 = ON;
DelayMS(500);
LED2 = OFF;
LED3 = ON;
DelayMS(500);
LED3 = OFF;
}
}
}
为什么单片机编程要尽量使用无符号类型的数据?
主要原因:多数情况下不需要用到负数,而单片机内存有限,用无符号类型的数据可以节省内存。
char型可以表示数的范围是-128到127,所占位数是8位
int型可以表示数的范围是-32768到32767,所占位数是16位
假如用有符号的数据类型表示,如果要表示的数是128,那就需要定义int型,用了16位。
unsigned char型可以表示数的范围是0到255,所占位数是8位
int型可以表示数的范围是0到65535,所占位数是16位
假如用无符号的数据类型表示,如果要表示的数是128,那就定义unsigned char型就可以了,只用了8位。
while(1)的作用
while语句的原型是while(表达式),当表达式为非0值时,执行while语句中的嵌套语句,while(1)则一直执行嵌套语句,代码不再向下执行。
在单片机中使用while(1),大部分情况是为了防止程序跑飞。因为很多时候执行完某段程序后,单片机的程序指针PC并不会停止,仍然会从ROM中读取指令来执行,从而出现不确定的结果,加个while(1)就能让程序在执行完后在原地循环,防止跑飞。
调试代码时,为了检测一部分代码是否OK,防止后面的代码干扰执行结果,也会在观测点加上while(1);