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設定為輸出低電平,
編程習慣
在實際撰寫代碼之前,先說明一下博主本次單片機的編程習慣,
- 新建一個檔案夾LPC2124_CODE,包含RESOURSE、SYSTEM、USER這三個子檔案,

- 打開keil4,新建工程,選擇LPC2124,并將startup.s添加到工程中,



- 進入工程,右鍵進入manage components,添加三個分組RESOURSE、SYSTEM、USER,


- 在USER檔案夾中新建main.c,在SYSTEM檔案夾中新建sys.c和sys.h,在RESOURSE檔案夾中新建gpio.c和gpio.h





- 將main.c添加到之前創建的USER分組下,將sys.c添加到之前創建的SYSTEM分組下,將gpio.c添加到之前創建的RESOURSE分組下,



- 打開魔術棒,選擇C/C++的include paths,添加頭檔案的路徑(SYSTEM檔案夾以及RESOURSE檔案夾)包含進來,


代碼撰寫
- 在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
- 在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++);
}
- 在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
- 在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);
}
- 在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分組下,
- 添加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
- 添加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){
//
// }
- 添加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分組下,
- 添加timer.h的代碼,主要是函式的申明,
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void timer_st0(void);
void timer_st1(void);
#endif
- 添加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){
}
- 添加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個停止位,不使能奇偶校驗,禁止間隔發送,第一次需要使能訪問除數鎖存,


DLL和DLM是設定除數鎖存暫存器的即為波特率的系數,計算公式為:15000000/(16*波特率),15000000是時鐘為15MHZ,波特率常用的有9600,19200,38400,而DLL表示低八位,DLM表示高八位,在設定完除數鎖存暫存器之后需要將LCR暫存器的最高位置0,以禁止訪問除數鎖存,

IER暫存器只需要使能RDA就行,即為接收到資料就產生中斷,

以上暫存器設定完之后同樣我們需要使能中斷的相關暫存器以及引腳功能復用的暫存器,
代碼撰寫
需要說明的是本次實驗我們用到了雙機通信,因此主程式main.c有兩套,
- 添加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
- 添加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){
//
// }
- 將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
- 添加主機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);
}
}
- 添加從機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會改變一下狀態,

感謝各位讀者的閱讀,如有錯誤望指出,歡迎討論!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/273773.html
標籤:其他
