LPC2124单片机的基础操作——GPIO、外部中断、定时器和串口

   日期:2021-04-08     浏览:13    评论:0    
核心提示:LPC2124单片机的基础操作——GPIO、外部中断、定时器和串口LPC2124的简介LPC2124之GPIOGPIO简介编程习惯功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入LPC2124的简介这是我第一次在CSDN发博客,

LPC2124单片机的基础操作——GPIO、外部中断、定时器和串口

  • LPC2124的简介
  • LPC2124之GPIO
    • GPIO简介
    • 编程习惯
    • 代码编写
  • LPC2124之EINT(外部中断)
    • EINT简介
    • 代码编写
    • 电路仿真
  • LPC2124之TIMER(定时器)
    • TIMER简介
    • 代码编写
    • 电路仿真
  • LPC2124之UART(串口)
    • UART简介
    • 代码编写
    • 电路仿真

LPC2124的简介

这是我第一次在CSDN发博客,正好学校里面开了嵌入式实验课,根据自己的经验和理解就想着总结一下。废话就不多说了,LPC2124是恩智浦公司(就是那个给全国大学生智能车比赛赞助的公司)推出的一款ARM7架构的单片机,下图给出其特性介绍。


其实不难看出其各项参数算是比较落后的,零几年的时候可能大家学习单片机的思路就是先学个51单片机、AVR单片机打个基础再去学ARM7、ARM9架构按照这个顺序的单片机,后来ARM公司推出了Cortex系列的架构,完全改变了处理器市场和我们学习的思路。比如我当时本科学单片机的时候先学个51单片机,之后就是STM32系列全家桶,这也得益于cortex架构的优秀性能以及更低的价格。那既然课程安排使用的是LPC2124,正好网上资料不多的情况(对比之下,stm32的资料是真的多,可以说是单片机中的顶流了),博主就从一款单片机最基础的GPIO、外部中断、定时器以及uart串口来讲起。

LPC2124之GPIO

GPIO简介

GPIO(General-purpose input/output)就是通用输入输出的意思,任何一款单片机最最最最基本的功能,我们平时习惯上称之为IO口,可以输出高电平、低电平或者模拟量,再者检测IO口的电平等等。在使用LPC2124的IO口时,我们是通过操作寄存器的方式进行的,如果不太懂寄存器的话,简单的理解就是每个寄存器对应一种特有的功能,我们需要给寄存器赋值以实现其功能。下图给出GPIO相关的寄存器。





上图我们只需要关注port0和port1的寄存器,port2和port3是针对lpc22系列的单片机的,不用考虑。为了方便理解下图给出LPC2124的电路结构图。

不难看出LPC2124有P0和P1两类端口,其中P0有31个引脚,P1有16个引脚,再来看GPIO的寄存器。
IOPIN(实际操作,P0口对应IO0PIN,P1口对应IO1PIN)是用来读取引脚电平值,1为高电平,0为低电平,是只读类型,无法写入,复位值是0x00000000,32位,假设IO0PIN的值是0x00008000,也就是P0.15是高电平,其余都是低电平,因为32寄存器用16进制(0x)来表示就是八位,0x00008000用二进制表示就是00000000000000001000000000000000。
IODIR(实际操作,P0口对应IO0DIR,P1口对应IO1DIR)是设置引脚是输出(1)还是输入(0)的,读写类型,复位值0x00000000,32位,假设IO1DIR=0x00000002,就是将P1.1设置为输出。
IOSET(实际操作,P0口对应IO0SET,P1口对应IO1SET)是设置引脚输出高电平(1),0无意义,32位,假设IO0SET=0x00000002,就是将P0.1设置为输出高电平。
IOCLR(实际操作,P0口对应IO0CLR,P1口对应IO1CLR)是设置引脚输出低电平(1),0无意义,32位,假设IO0CLR=0x00000002,就是将P0.1设置为输出低电平。

编程习惯

在实际编写代码之前,先说明一下博主本次单片机的编程习惯。

  1. 新建一个文件夹LPC2124_CODE,包含RESOURSE、SYSTEM、USER这三个子文件。
  2. 打开keil4,新建工程,选择LPC2124,并将startup.s添加到工程中。


  3. 进入工程,右键进入manage components,添加三个分组RESOURSE、SYSTEM、USER。

  4. 在USER文件夹中新建main.c,在SYSTEM文件夹中新建sys.c和sys.h,在RESOURSE文件夹中新建gpio.c和gpio.h




  5. 将main.c添加到之前创建的USER分组下,将sys.c添加到之前创建的SYSTEM分组下,将gpio.c添加到之前创建的RESOURSE分组下。


  6. 打开魔术棒,选择C/C++的include paths,添加头文件的路径(SYSTEM文件夹以及RESOURSE文件夹)包含进来。

代码编写

  1. 在sys.h中添加下面的代码。其中#ifndef#define#endif结构主要是为了防止重复定义的错误,其余的主要是进行一些类型定义以及sys.c的函数的申明。
#ifndef __SYS_H
#define __SYS_H 
#include "LPC21xx.H"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
void delay_ms(u32 num); //ms级粗延迟,最小单位为1ms,无法小数延迟
void delay_s(u32 num); //s级粗延迟,建议最小单位为s,可以小数延迟,最小小数点后三位
#endif
  1. 在sys.c中添加下面的代码。主要是进行了ms级以及s级的粗略延时函数的编写。
#include "sys.h"
u32 times = 100;//循环次数默认为100
void delay_ms(u32 num){                          
  u32 i,j;
  for(i=0;i<(num * 22);i++)
  for(j=0;j<times;j++);
}
void delay_s(u32 num){                          
  u32 i,j;
  for(i=0;i<(num * 22000);i++)
  for(j=0;j<times;j++);
}
  1. 在gpio.h中添加下面的代码。主要是对gpio.c的函数进行申明,包含了P0/P1口引脚电平设置为高电平(gpio0_seH、gpio1_seH),P0/P1口引脚电平设置为低电平(gpio0_seL、gpio1_seL),判断P0/P1口引脚电平(gpio0_geN、gpio1_geN)。
#ifndef __GPIO_H
#define __GPIO_H
#include "sys.h"

void gpio0_seH(u16 pin_num); //设置GPIO0引脚为高电平
void gpio0_seL(u16 pin_num); //设置GPIO0引脚为低电平
u32 gpio0_geN(u16 pin_num); //读取GPIO0引脚电平
void gpio1_seH(u16 pin_num); //设置GPIO1引脚为高电平
void gpio1_seL(u16 pin_num); //设置GPIO1引脚为低电平
u32 gpio1_geN(u16 pin_num); //读取GPIO1引脚电平

#endif
  1. 在gpio.c中添加下面的代码。主要是对gpio的寄存器操作封装为函数操作,读者完全可以通过函数调用的方式进行管脚的操作。读者如果对位操作不太清楚的话,可以简单的理解为左移就是右填0,右移就是左填零,按位或就是置1,按位与就是清零(取位),按位取反就是字面意思。
#include "gpio.h"
//pin_num: 0~31,选择引脚号
void gpio0_seH(u16 pin_num){ 
	if(pin_num >= 16 && pin_num <= 31){ 
		PINSEL1 = PINSEL1 & (~(0x00000003 << (2 * (pin_num - 16))));//设置引脚为GPIO模式
	}
	else{ 
		PINSEL0 = PINSEL0 & (~(0x00000003 << (2 * pin_num)));//设置引脚为GPIO模式
	}
	IO0DIR = IO0DIR | (0x00000001 << pin_num);//方向控制寄存器,1位输出,0位输入
	IO0SET = IO0SET | (0x00000001 << pin_num);//高电平输出寄存器
}
void gpio0_seL(u16 pin_num){ 
	if(pin_num >= 16 && pin_num <= 31){ 
		PINSEL1 = PINSEL1 & (~(0x00000003 << (2 * (pin_num - 16))));//设置引脚为GPIO模式
	}
	else{ 
		PINSEL0 = PINSEL0 & (~(0x00000003 << (2 * pin_num)));//设置引脚为GPIO模式
	}
	IO0DIR = IO0DIR | (0x00000001 << pin_num);
	IO0CLR = IO0CLR | (0x00000001 << pin_num);//低电平输出寄存器
}
u32 gpio0_geN(u16 pin_num){ 
	if(pin_num >= 16 && pin_num <= 31){ 
		PINSEL1 = PINSEL1 & (~(0x00000003 << (2 * (pin_num - 16))));//设置引脚为GPIO模式
	}
	else{ 
		PINSEL0 = PINSEL0 & (~(0x00000003 << (2 * pin_num)));//设置引脚为GPIO模式
	}
	IO0DIR = IO0DIR & (~(0x00000001 << pin_num));
	return ((IO0PIN & (0x00000001 << pin_num)) >> pin_num);//返回指定管脚上的电平值
}
void gpio1_seH(u16 pin_num){ 
	if(16 <= pin_num <= 25){ 
		PINSEL2 = PINSEL2 & (~(0x00000008));//设置引脚为GPIO模式
	}
	else{ 
		PINSEL2 = PINSEL2 & (~(0x00000004));//设置引脚为GPIO模式
	}
	IO1DIR = IO1DIR | (0x00000001 << pin_num);
	IO1SET = IO1SET | (0x00000001 << pin_num);
}
void gpio1_seL(u16 pin_num){ 
	if(16 <= pin_num <= 25){ 
		PINSEL2 = PINSEL2 & (~(0x00000008));
	}
	else{ 
		PINSEL2 = PINSEL2 & (~(0x00000004));
	}
	IO1DIR = IO1DIR | (0x00000001 << pin_num);
	IO1CLR = IO1CLR | (0x00000001 << pin_num);
}
u32 gpio1_geN(u16 pin_num){ 
	if(16 <= pin_num <= 25){ 
		PINSEL2 = PINSEL2 & (~(0x00000008));
	}
	else{ 
		PINSEL2 = PINSEL2 & (~(0x00000004));
	}
	IO1DIR = IO1DIR & (~(0x00000001 << pin_num));
	return ((IO1PIN & (0x00000001 << pin_num)) >> pin_num);
}
  1. 在main.c中添加下面的代码。

//本程序仅供学习科研使用
//单片机型号:LPC2124
//作者:哈哈我是HH
//修改日期:2021.4.6
//版本:V1.0
//Copyright(C) 哈哈我是HH 2021-2031
//All rights reserved

#include "sys.h"
#include "gpio.h"
u32 flag1 = 0;
int main(void){ 
	while (1)  
	{ 
		gpio0_seH(0);
		delay_ms(200);
		gpio0_seL(0);
		delay_ms(200);
		flag1 = gpio1_geN(16);
		if(flag1 == 1){ 
			gpio0_seH(1);
		}
		if(flag1 == 0){ 
			gpio0_seL(1);
		}
	}
}

GPIO的操作是非常简单的,这里就不添加proteus中的仿真图,大家可以自己修改main.c中的代码进行实验。接下来就要是介绍外部中断的使用了。

LPC2124之EINT(外部中断)

EINT简介

中断学得好不好,可以说是任何一款单片机有没有入门的试金石(感觉定时器和串口都是试金石,算了算了,想想还是中断更加基础)。单片机的程序执行都是main函数开始的(排除一些上电片内资源的调用),因为c语言的顺序执行原因,导致main函数的所有步骤都是要一步一步顺序执行的,所以既然你写了,那都要执行,这样的效率简直太低了。设想一下如果你的main函数里面写了100条操作语句,结果只有最后一条语句才真正去实际操作的,前面的99条都是当前不必执行的,可能100条对于单片机来说还是小意思,那更多的呢?这时候如果引入了中断,那么完全不用都在main函数里面去书写,全部放到中断中,哪条响应中断,哪条才去执行,这样单片机的效率真正的提高了!
网上比较形象的例子就是如果你在打游戏但是随时都有可能女朋友(假设你有女朋友)的微信要回,你不可能打1s的游戏看1s的手机消息(相当于main函数里面while(1)的操作,但是如果你真的这么去做,我只能说你是好男人,博主不配拥有女朋友),你一定是一直在打游戏,只有手机来消息了你才会去看(相当于中断操作)!
LPC2124的外部中断总共有4个,为P0.1、P0.3、P0.7、P0.9、P0.14、P0.15、P0.16、P0.20引脚,因此是需要设置PINSEL寄存器的值为EINT。

下图给出了外部中断的寄存器。

EXTINT是设置中断标志的,8位,高电平有效。

EXTMODE是设置外部信号的中断响应是电平有效(0)还是边沿有效(1),同样也是8位。

EXTPOLAR是设置外部信号的中断响应如果是电平有效,那么这个信号是低电平有效(0)还是高电平有效(1),中断响应如果是边沿有效,那么这个信号是下降沿有效(0)还是上升沿有效(1),同样也是8位。

下图给出了向量中断寄存器。向量中断寄存器是所有中断(包括外部中断、定时器中断、串口中断等等)都需要设置的。主要是设置中断的类型,中断函数的地址以及中断的优先级。

我们在配置向量中断寄存器主要是设置四个寄存器。
VICIntSelect配置中断类型,FIQ是快速中断请求,一般我们选择的是IRQ(向量中断请求)。

VICIntEnable是使能中断,我们配置为1就行。

VICVectCntl设置中断优先级,第5位给1使能,4:0位设置为对应中断的中断源编号(见下图),实际中VICVectCntl对应的寄存器是VICVectCntl0~15,也就是对应16个优先级。数字越大优先级越低,数字越小优先级越高,这样的话在中断都响应的情况下,优先级越高越先响应,值得注意的是不能给不同中断设置相同的优先级。

中断源编号


VICVectAddr是设置中断服务函数的地址,因为优先级的原因,所以这里也是有16个,即寄存器是VICVectAddr0~15,而优先级和地址的标号是要相同的。

以上就是外部中断需要配置的寄存器,看上去比较多,但是实际上手还是比较容易的,配置比较简单。

代码编写

在编写编写前,我们需要在GPIO的程序上继续编写,首先在RESOURSE文件下添加两个文件eint.c和eint.h,并在工程中将eint.c添加到RESOURSE分组下。

  1. 添加eint.h的代码,是一些外部中断函数的申明。
#ifndef __EINT_H
#define __EINT_H
#include "sys.h"

void eint_st0(u32 pin_num);//外部中断0,pin_num:1或者16
void eint_st1(u32 pin_num);//外部中断1,pin_num:3或者14
void eint_st2(u32 pin_num);//外部中断2,pin_num:7或者15
void eint_st3(u32 pin_num);//外部中断3,pin_num:9或者20

#endif
  1. 添加eint.c的代码,主要是外部中断函数的编写设计我之前说的一些寄存器的操作。需要说明的是,如果中断服务函数不需要要的是需要写个框架的,代码最下面我给了模板,直接去掉//就行。
#include "eint.h"
__irq void IRQ_Eint0(void);//外部中断0中断服务函数声明
__irq void IRQ_Eint1(void);//外部中断1中断服务函数声明
__irq void IRQ_Eint2(void);//外部中断2中断服务函数声明
__irq void IRQ_Eint3(void);//外部中断3中断服务函数声明
//外部中断0只能选择1或者16
void eint_st0(u32 pin_num){ 
	if(pin_num == 1){ 
		PINSEL0 = PINSEL0 | 0x0000000C; //P0.1管脚功能选择为外部中断
	}
	else{ 
		PINSEL1 = PINSEL1 | 0x00000003; //P0.16管脚功能选择为外部中断
	}
		VICIntSelect = VICIntSelect & (~0x00004000); //将外部中断0设置为IRQ模式
		VICIntEnable = VICIntEnable | 0x00004000; //使能外部中断0
		VICVectCntl0 = 0x2E; //将14号中断外部中断0优先级设置为0
		VICVectAddr0 = (int)IRQ_Eint0; //设置中断服务子程序
		EXTINT = EXTINT | 0x01;
		EXTMODE = EXTMODE & (~0x01); //0为电平激活,1为边沿激活
		EXTPOLAR = EXTPOLAR & (~0x01); //0为低电平产生中断,1位高电平产生中断
}
//外部中断1只能选择3或者14
void eint_st1(u32 pin_num){ 
	if(pin_num == 3){ 
		PINSEL0 = PINSEL0 | 0x000000C0; //P0.3管脚功能选择为外部中断
	}
	else{ 
		PINSEL0 = PINSEL0 | 0x30000000; //P0.14管脚功能选择为外部中断
	}
		VICIntSelect = VICIntSelect & (~0x00008000); //将外部中断1设置为IRQ模式
		VICIntEnable = VICIntEnable | 0x00008000; //使能外部中断1
		VICVectCntl1 = 0x2F; //将15号中断外部中断1优先级设置为1
		VICVectAddr1 = (int)IRQ_Eint1; //设置中断服务子程序
		EXTINT = EXTINT | 0x02;
		EXTMODE = EXTMODE & (~0x02); //0为电平激活,1为边沿激活
		EXTPOLAR = EXTPOLAR & (~0x02); //0为低电平产生中断,1位高电平产生中断
}
//外部中断2只能选择7或者15
void eint_st2(u32 pin_num){ 
	if(pin_num == 7){ 
		PINSEL0 = PINSEL0 | 0x0000C000; //P0.7管脚功能选择为外部中断
	}
	else{ 
		PINSEL0 = PINSEL0 | 0xC0000000; //P0.15管脚功能选择为外部中断
	}
		VICIntSelect = VICIntSelect & (~0x00010000); //将外部中断2设置为IRQ模式
		VICIntEnable = VICIntEnable | 0x00010000; //使能外部中断2
		VICVectCntl2 = 0x30; //将16号中断外部中断2优先级设置为2
		VICVectAddr2 = (int)IRQ_Eint2; //设置中断服务子程序
		EXTINT = EXTINT | 0x04;
		EXTMODE = EXTMODE & (~0x04); //0为电平激活,1为边沿激活
		EXTPOLAR = EXTPOLAR & (~0x04); //0为低电平产生中断,1位高电平产生中断
}
//外部中断3只能选择9或者20
void eint_st3(u32 pin_num){ 
	if(pin_num == 9){ 
		PINSEL0 = PINSEL0 | 0x000C0000; //P0.9管脚功能选择为外部中断
	}
	else{ 
		PINSEL1 = PINSEL1 | 0x00000300; //P0.20管脚功能选择为外部中断
	}
		VICIntSelect = VICIntSelect & (~0x00020000); //将外部中断3设置为IRQ模式
		VICIntEnable = VICIntEnable | 0x00020000; //使能外部中断3
		VICVectCntl3 = 0x31; //将17号中断外部中断3优先级设置为3
		VICVectAddr3 = (int)IRQ_Eint3; //设置中断服务子程序
		EXTINT = EXTINT | 0x08;
		EXTMODE = EXTMODE & (~0x08); //0为电平激活,1为边沿激活
		EXTPOLAR = EXTPOLAR & (~0x08); //0为低电平产生中断,1位高电平产生中断
}
//外部中断0中断服务函数
// __irq void IRQ_Eint0(void){ 
// 
// }
//外部中断1中断服务函数
// __irq void IRQ_Eint1(void){ 
// 
// }
//外部中断2中断服务函数
// __irq void IRQ_Eint2(void){ 
// 
// }
//外部中断3中断服务函数
// __irq void IRQ_Eint3(void){ 
// 
// }
  1. 添加main.c的代码,这里需要说明的是一般外部中断的中断服务函数是进行标志位操作的,一般不会进行大量的循环之类。

//本程序仅供学习科研使用
//单片机型号:LPC2124
//作者:哈哈我是HH
//修改日期:2021.4.6
//版本:V1.0
//Copyright(C) 哈哈我是HH 2021-2031
//All rights reserved

#include "sys.h"
#include "gpio.h"
#include "eint.h"
u32 flag = 0;
__irq void IRQ_Eint0(void){ 
	flag = 1;
	while((EXTINT&0x01)!=0){   //等待外部中断信号恢复为高电平
  	EXTINT = 0x01; //清除EINT0中断标志
  }
  VICVectAddr=0;
}
__irq void IRQ_Eint1(void){ 
	flag = 2;
	while((EXTINT&0x02)!=0){   //等待外部中断信号恢复为高电平
  	EXTINT = 0x02; //清除EINT1中断标志
  }
  VICVectAddr=0;
}
__irq void IRQ_Eint2(void){ 
	flag = 3;
	while((EXTINT&0x04)!=0){   //等待外部中断信号恢复为高电平
  	EXTINT = 0x04; //清除EINT2中断标志
  }
  VICVectAddr=0;
}
__irq void IRQ_Eint3(void){ 
	flag = 4;
	while((EXTINT&0x08)!=0){   //等待外部中断信号恢复为高电平
  	EXTINT = 0x08; //清除EINT3中断标志
  }
  VICVectAddr=0;
}
int main(){ 
	eint_st0(16);
	eint_st1(3);
	eint_st2(7);
	eint_st3(20);
	while(1){ 
		if(flag == 1){ 
			gpio1_seL(23);
			delay_ms(100);
			gpio1_seH(23);
			delay_ms(100);		
		}
		else if(flag == 2){ 
			gpio1_seL(23);
			delay_ms(500);
			gpio1_seH(23);
			delay_ms(500);
		}
		else if(flag == 3){ 
			gpio1_seL(23);
			delay_s(1);
			gpio1_seH(23);
			delay_s(1);
		}
		else if(flag == 4){ 
			gpio1_seL(23);
			delay_s(2);
			gpio1_seH(23);
			delay_s(2);
		}
		else{ 
			gpio1_seH(23);
		}
	}
}

编译之后就可以电路仿真了,这里使用的是proteus(这软件真就一言难尽)。

电路仿真


实验现象:
按下第一个按键,led以100ms进行闪烁。
按下第二个按键,led以500ms进行闪烁。
按下第三个按键,led以1s进行闪烁。
按下第四个按键,led以2s进行闪烁。

LPC2124之TIMER(定时器)

TIMER简介

定时器的概念其实非常简单就是单片机内部的一个精确计数(定时)的资源。细心的读者不难发现之前我在编写sys.c的代码就有写到delay函数,这是粗延时,简单的理解就是单片机在一个地方死循环,而定时器往往搭配中断组成定时器中断的功能来使用,比如在经过一段时间后定时器产生中断响应单片机去执行。当然了定时器最基本的功能就是计时,其衍生功能还有输入捕获和pwm波输出。
LPC2124有两个定时器TIMER0和TIMER1,其时钟来源于外部晶振。我们也同样是进行寄存器的操作来实现相应的功能。下图给出了定时器的相关寄存器。


实际上我们在操作定时器时不需要都配置上面全部的寄存器,有些是输入捕获实验的,仅仅拿来计时的话只有四个寄存器需要配置。
MR寄存器是设置定时器的计数值,记一次相当于时钟的一个周期,实际配置我们设置的是T0/1MR0~3。

MCR寄存器的配置主要是如果计数器和MR0~3的值匹配后会产生中断或者复位或者停止。这里是需要和MR寄存器的编号相同,实际操作中我们需要配置的是T0MCR或者T1MCR。


TCR寄存器的作用是使能定时器,置1就行。实际操作需要配置的是T0TCR或者T1TCR。

IR寄存器的配置是如果将MCR寄存器设置了中断模式,那么这里是需要匹配中断通道的,例如MCR设置了MR0的中断模式,那么IR寄存器就要匹配通道0的中断标志。
既然上文博主提到了定时器一般是需要中断的,那么也就是说除了上面的四个寄存器需要配置,还需要中断的四个寄存器VICIntSelect、VICIntEnable、VICVectAddr、VICVectCntl,这一部分的知识如果不太清楚的话就请查看一下上文的外部中断寄存器配置的讲解。
接下来就是代码的编写了。

代码编写

同样我们是需要在外部中断实验的基础再添加代码的,首先在RESOURSE文件下添加两个文件timer.c和timer.h,并在工程中将timer.c添加到RESOURSE分组下。

  1. 添加timer.h的代码。主要是函数的申明。
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void timer_st0(void);
void timer_st1(void);
#endif
  1. 添加timer.c的代码。编写了定时器初始化函数,需要说明的是中断优先级和中断服务函数需要和外部中断的区别开,不能设置相同的中断优先级和中断服务函数。
#include "timer.h"
__irq void IRQ_timer0 (void);//定时器0中断服务函数
__irq void IRQ_timer1 (void);//定时器1中断服务函数
void timer_st0(void){  
  T0MR0 = 1199999; //设置匹配值,1200000-1,12000000=1s
  T0MCR = T0MCR | 0x003; //匹配后复位TC,并产生中断
  T0TCR = 0x1;//启动定时器0,T0TCR计数控制寄存器
  VICIntSelect = VICIntSelect & (~0x00000010); //将定时器0设置为IRQ模式
  VICIntEnable = VICIntEnable | 0x00000010; //使能定时器0
  VICVectAddr2 = (int)IRQ_timer0; //设置定时器0的中断服务程序地址 
  VICVectCntl2 = 0x24; //将4号中断定时器0优先级设置为2
}
void timer_st1(void){  
  T1MR0 = 1199999; //设置匹配值,1200000-1,12000000=1s
  T1MCR = T1MCR | 0x003; //匹配后复位TC,并产生中断
  T1TCR = 0x1;//启动定时器1,T1TCR计数控制寄存器
  VICIntSelect = VICIntSelect & (~0x00000020); //将定时器1设置为IRQ模式
  VICIntEnable = VICIntEnable | 0x00000020; //使能定时器1
  VICVectAddr3 = (int)IRQ_timer1; //设置定时器1的中断服务程序地址 
  VICVectCntl3 = 0x25; //将5号中断定时器1优先级设置为1
}
// __irq void IRQ_timer0 (void){ 
// 
// }
__irq void IRQ_timer1 (void){ 
	
}
  1. 添加main.c的代码。需要说明的是需要在main函数开始的时候初始化外部中断(本实验用到了外部中断)和定时器。

//本程序仅供学习科研使用
//单片机型号:LPC2124
//作者:哈哈我是HH
//修改日期:2021.4.6
//版本:V1.0
//Copyright(C) 哈哈我是HH 2021-2031
//All rights reserved

#include "sys.h"
#include "gpio.h"
#include "timer.h"
#include "eint.h"
u32 timer0Times = 0;
u32 times_flag = 0;	
__irq void IRQ_Eint2(void){ 
	times_flag = 1;
	while((EXTINT&0x04)!=0){ 		//中断标志清零
		EXTINT=0x04;
	}
  VICVectAddr=0;					//通知中断处理结束
}	
__irq void IRQ_Eint3(void){ 
	times_flag = 2;
	while((EXTINT&0x08)!=0){ 		//中断标志清零
		EXTINT=0x08;
	}
  VICVectAddr=0;					//通知中断处理结束
}
__irq void IRQ_timer0(void){  //定时器中断处理函数
	if(times_flag == 1){ 
		timer0Times++;
		if(timer0Times == 1){ 								
			gpio0_seH(0);
			gpio0_seL(1);
			gpio0_seH(2);
			gpio0_seL(3);
		}
		if(timer0Times == 2){ 	
			gpio0_seL(0);
			gpio0_seH(1);
			gpio0_seL(2); 
			gpio0_seH(3);
		}
		if(timer0Times == 3){ 	
			gpio0_seH(0);
			gpio0_seL(1);
			gpio0_seH(2); 
			gpio0_seL(3);
		}
		if(timer0Times == 4){ 	
			gpio0_seL(0);
			gpio0_seH(1);
			gpio0_seL(2); 
			gpio0_seH(3);
			timer0Times = 0;
		}
		T0IR = 1;						//中断标志清零
		VICVectAddr = 0;    //通知中断处理结束
	}
	else if(times_flag == 2){ 
		timer0Times++;
		if(timer0Times == 1){ 								
			gpio0_seH(0);
			gpio0_seH(1);
			gpio0_seL(2);
			gpio0_seL(3);
		}
		if(timer0Times == 2){ 	
			gpio0_seL(0);
			gpio0_seL(1);
			gpio0_seH(2);
			gpio0_seH(3);
		}
		if(timer0Times == 3){ 	
			gpio0_seH(0);
			gpio0_seH(1);
			gpio0_seL(2);
			gpio0_seL(3);
		}
		if(timer0Times == 4){ 	
			gpio0_seL(0);
			gpio0_seL(1);
			gpio0_seH(2);
			gpio0_seH(3);
			timer0Times = 0;
		}
		T0IR = 1;						//中断标志清零
		VICVectAddr = 0;    //通知中断处理结束
	}
	else{ 
		T0IR = 1;						//中断标志清零
		VICVectAddr = 0;    //通知中断处理结束
	}
}
int main(void){ 
	gpio0_seH(0);
	gpio0_seH(1);
	gpio0_seH(2);
	gpio0_seH(3);
	eint_st2(15);
	eint_st3(20);		//按键中断初始化
	timer_st0();		//定时器初始化
	while (1)  
	{ 
		if(times_flag == 1){ 
			gpio1_seL(27);
		}
		if(times_flag == 2){ 
			gpio1_seH(27);
		}
	}
}

电路仿真


实验说明:需要将LPC2124的时钟设置为12MHZ。
实验现象:
按下第一个按键,led1、led3和led2、led4交替闪烁,D5亮。
按下第二个按键,led1、led2和led3、led4交替闪烁,D5灭。

LPC2124之UART(串口)

UART简介

串口主要是用来做串口通信的,而通信正是人机交互、多机互联的基础,只有学好了uart串口通信才能够更加深入的去学习其他的通信方式,比如说IIC、SPI等等。
在介绍UART寄存器之前还是需要了解一些串口的基本知识,比如波特率,还有就是其数据传输结构,当然了有一点是需要说明的是通常意义上的UART串口通信应该是半双工异步通信,何为半双工,简单的理解就是收发不能同步,何为异步,简单的理解就是不存在同步时钟,因此才会需要波特率去匹配数据收发速度,与之对应的USART才是全双工异步通信。
波特率更深刻的理解可以自行百度,这里大家只需要知道波特率是代替时钟的作用,否则两边数据“理解”无法做到匹配,举个简单的理解我传输了一个1去2号机,那2号机怎么知道这是1个1还是2个1还是n个1,如果我们约定好波特率了,就不会存在问题,我只要以和发送方相同的发送步长(波特率)去读就不会出错了。
数据传输结构指的是一次传输的数据格式,通常设定的是8位数据,无校验位,一个停止位。
LPC2124总共有两组串口分别为UART0——RXD0(接收P0.1)、TXD0(发送P0.0)和UART1——RXT0(接收P0.9)、TXD0(发送P0.8),接下来给出串口的所有寄存器(以串口0介绍,串口1同理)。

以上的寄存器我们也并不是都用到。
LCR寄存器是用来配置数据传输结构,这里就设置为8个字符长度、1个停止位,不使能奇偶校验,禁止间隔发送,第一次需要使能访问除数锁存。


DLLDLM是设置除数锁存寄存器的即为波特率的系数,计算公式为:15000000/(16*波特率),15000000是时钟为15MHZ,波特率常用的有9600,19200,38400,而DLL表示低八位,DLM表示高八位。在设置完除数锁存寄存器之后需要将LCR寄存器的最高位置0,以禁止访问除数锁存。

IER寄存器只需要使能RDA就行,即为接收到数据就产生中断。

以上寄存器设置完之后同样我们需要使能中断的相关寄存器以及引脚功能复用的寄存器。

代码编写

需要说明的是本次实验我们用到了双机通信,因此主程序main.c有两套。

  1. 添加uart.h的代码。主要是函数的申明。
#ifndef __UART_H
#define __UART_H
#include "sys.h"
void uart_st0(u32 baud_rate); //不要设置波特率低于4800,不稳,常用9600,19200,38400
void uart_st1(u32 baud_rate); //不要设置波特率低于4800,不稳,常用9600,19200,38400
u32 putchar0(u32 ch);
void serialPuts0(u8 *p);
u32 putchar1(u32 ch);
void serialPuts1(u8 *p);
__irq void IRQ_uart0 (void);//串口0中断服务函数

#endif
  1. 添加uart.c的代码。主要是一些串口初始化以及发送字符串的函数的编写。
#include "uart.h"
__irq void IRQ_uart0 (void);//串口0中断服务函数
void uart_st0(u32 baud_rate){  //串口0初始化
	PINSEL0 = PINSEL0 | 0x00000005; //打开串口功能
  U0LCR = 0x83; //8位数据,无校验位,一个停止位,禁止间隔发送,使能访问除数锁存
	if(15000000/(16 * baud_rate) <= 255){ 
		U0DLM = 0; //设置除数锁存高八位
		U0DLL = 15000000/(16 * baud_rate);//设置除数锁存低八位,时钟15MHz设置波特率为38400,计算公式15000000/(16*波特率)
	}else{ 
		U0DLM = (floor)(15000000/(16 * baud_rate) - 255); //设置除数锁存高八位
		U0DLL = 255;//设置除数锁存低八位,时钟15MHz设置波特率为38400,计算公式15000000/(16*波特率)
	}
  U0LCR = 0x03; //禁止访问除数锁存
// U0IIR = 0x04; //中断标识寄存器为接收数据可用
	U0IER = 0x01; //使能RDA中断
	VICIntSelect = VICIntSelect & (~0x00000040); //将串口0设置为IRQ模式
	VICIntEnable = VICIntEnable | 0x00000040; //使能串口0中断
  VICVectAddr0 = (int)IRQ_uart0; //设置串口0的中断服务程序地址 
  VICVectCntl0 = 0x26; //将6号中断串口0优先级设置为0
}
void uart_st1(u32 baud_rate){  //串口1初始化
	PINSEL0 = PINSEL0 | 0x00050000; //打开串口功能
  U1LCR = 0x83; //8位数据,无校验位,一个停止位,禁止间隔发送,使能访问除数锁存
	if(15000000/(16 * baud_rate) <= 255){ 
		U1DLM = 0; //设置除数锁存高八位
		U1DLL = 15000000/(16 * baud_rate);//设置除数锁存低八位,时钟15MHz设置波特率为38400,计算公式15000000/(16*波特率)
	}else{ 
		U1DLM = (floor)(15000000/(16 * baud_rate) - 255); //设置除数锁存高八位
		U1DLL = 255;//设置除数锁存低八位,时钟15MHz设置波特率为38400,计算公式15000000/(16*波特率)
	}
  U1LCR = 0x03; //禁止访问除数锁存
}
u32 putchar0(u32 ch){  //向串口0发送一个字符
  if(ch == '\n'){ 
    while (!(U0LSR & 0x20)); //帧错误状态激活(停止位错误)
    U0THR = 0x0D; //回车
		U0THR = 0x0A; //换行
  }
  while (!(U0LSR & 0x20)); //帧错误状态激活(停止位错误)
  return (U0THR = ch);
}
void serialPuts0(u8 *p){  //向串口0发送字符串
  while (*p != '\0'){ 
  	putchar0(*p++);
  }
}
u32 putchar1(u32 ch){  //向串口1发送一个字符
  if(ch == '\n'){ 
    while (!(U1LSR & 0x20)); //帧错误状态激活(停止位错误)
    U1THR = 0x0D; //回车
		U1THR = 0x0A; //换行
  }
  while (!(U1LSR & 0x20)); //帧错误状态激活(停止位错误)
  return (U1THR = ch);
}
void serialPuts1(u8 *p){  //向串口1发送字符串
  while (*p != '\0'){ 
  	putchar1(*p++);
  }
}
// __irq void IRQ_uart0 (void){ 
// 
// }
  1. 将sys.h修改一下。
#ifndef __SYS_H
#define __SYS_H 
#include "LPC21xx.H"
#include "math.h"
#include "string.h"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
void delay_ms(u32 num); //ms级粗延迟,最小单位为1ms,无法小数延迟
void delay_s(u32 num); //s级粗延迟,建议最小单位为s,可以小数延迟,最小小数点后三位
#endif
  1. 添加主机main.c的代码。

//本程序仅供学习科研使用
//单片机型号:LPC2124
//作者:哈哈我是HH
//修改日期:2021.3.29
//版本:V1.0
//Copyright(C) 哈哈我是HH 2021-2031
//All rights reserved

#include "sys.h"
#include "gpio.h"
#include "timer.h"
#include "eint.h"
#include "uart.h"
u8 ledDown[]={ "The LED is down!\n"};
u8 ledUp[]={ "The LED is up!\n"};
char ledon[] = "dt1"; //尽量输入数字,字母在proteus中不稳定
char ledoff[] = "dt0"; //尽量输入数字,字母在proteus中不稳定
char buff[] = "";
u32 timesflag = 0;
__irq void IRQ_uart0 (void){ 
	if(U0RBR != '!'){ 
		buff[timesflag] = U0RBR;
		timesflag++;
	}else{ 
		if(strcmp(ledon,buff) == 0){ 
			gpio0_seL(25);
		}else{ 
			gpio0_seH(25);
		}
		timesflag = 0;
	}
	VICVectAddr=0;
}
int main (void){ 
	uart_st0(38400);
	uart_st1(38400);
  while(1){ 
		gpio0_seL(18);
		serialPuts1(ledUp);
		delay_ms(200);
  	gpio0_seH(18);
		serialPuts1(ledDown);
		delay_ms(200);
  }
}
  1. 添加从机main.c的代码。

//本程序仅供学习科研使用
//单片机型号:LPC2124
//作者:哈哈我是HH
//修改日期:2021.3.29
//版本:V1.0
//Copyright(C) 哈哈我是HH 2021-2031
//All rights reserved

#include "sys.h"
#include "gpio.h"
#include "timer.h"
#include "eint.h"
#include "uart.h"
u8 ledUp[]={ "dt1!"}; //尽量输入数字,字母在proteus中不稳定,结尾以!区分
u8 ledDown[]={ "dt0!"}; //尽量输入数字,字母在proteus中不稳定,结尾以!区分
u32 key_flag = 0;
__irq void IRQ_Eint2(void){ 
	if(key_flag == 0){ 
		serialPuts0(ledUp);
	}else{ 
		serialPuts0(ledDown);
	}
	key_flag = !key_flag;
	while((EXTINT&0x04)!=0){ 		//中断标志清零
		EXTINT=0x04;
	}
  VICVectAddr=0;					//通知中断处理结束
}
int main (void){ 
	uart_st0(38400);
	eint_st2(15);
  while(1){ 
// serialPuts0(ledDown);
// delay_ms(200);
// serialPuts0(ledUp);
// delay_ms(200);
  }
}

电路仿真


实验现象:
D1闪烁的同时会伴随virtual terminal上面的字符提醒。同时每按一下从机的按键,主机的D1会改变一下状态。

感谢各位读者的阅读,如有错误望指出,欢迎讨论!

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服