第一章 编写第一个单片机程序(汇编语言篇)
据我所知,有经验的编程人员最讨厌空洞地说教。他们希望看到你写实实在在的程序。因此我决定不按一般地编写单片机书籍的常规行事。先写这一章----编写第一个单片机程序。让读者首先对开发单片机程序的一般过程及keilC开发环境有所了解。
遵循丹尼斯·里奇(Dennis Ritchie)的示例。在开始学习一种新的编程语言时,一般都首先创建一个简单的“Hello,World”程序。我也不想破坏这种传统。不要以为只有PC机的编程语言才能编写“Hello,World”程序,单片机上电后,一样可以通过串口或者其它通信端口发送“Hello,World”信息,然后用常用的串口调试助手之类的应用程序进行捕捉显示。
在进行你的第一个单片机程序编写之前,要先确定你是否有一个实际的程序运行环境。因此在开始写程序之前,建议大家一定要先按照附录D内“手把手教你如何搭建一个单片机程序运行环境”一文,然后根据所教授的方法搭建一个属于自己的程序运行平台,再继续下面的程序编写。
以下是具体的实现过程:
1、首先安装大名鼎鼎的keil软件。
2、在桌面上运行快捷方式:,出现如下图的操作环境。
3、现在按照如下顺序开始新建一个项目。
b.选择New Project,出现窗口
c.在文件名栏,写入项目名称helloworld,然后点击保存。
之后出现选单,要求选择你所使用的单片机型号。(本书介绍的是LPC93X系列单片,选用的型号为LPC936)。
点击确定。出现窗口:
由于这次的例子代码是用汇编语言写的ASM文件,因此可以选择否。
此时项目已经建好:
但是此为一个空项目,需要新建文件以及加入文件。
d.新建文件:
如图点击选择Create a new file。
出现窗口Text1。
如图选择Save As。
在出现上图后,由于你将建立的是一个汇编程序文件,因此就可以在文件名处输入“helloworld.asm”。
此时,你的汇编程序文件已经建立起了。
e.将建立的helloworld.asm加入刚才建立的项目中,过程如图:
在建立的source group文件夹上点右键弹出选单,然后选择Add Files to Group’Source Group1’。
出现窗口,在文件类型处选择ASM Source,就会出现helloworld.asm文件。
选择helloworld.asm点击Add。
现在就可以开始你的第一个程序编写了。
1、先将下列程序输入KEIL程序编译器内,如下图:
图1.1
源程序如下:
/* BYTE Registers */
P0M1 EQU 84H
P0M2 EQU 85H
P1M1 EQU 91H
P1M2 EQU 92H
P2M1 EQU 0A4H
P2M2 EQU 0A5H
P3M1 EQU 0B1H
P3M2 EQU 0B2H
BRGR0 EQU 0BEH
BRGR1 EQU 0BFH
BRGCON EQU 0BDH
SSTAT EQU 0BAH
ESR EQU 0ACH
EST EQU 0EDH
;===================================================================
;开始程序
ORG 0000H
LJMP MAIN
ORG 0080H
MAIN: MOV SP,#07H ;设置堆栈首地址
LCALL INITIAL_IO ;初始化IO端口
LCALL UART_INIT ;初始化串口
SEND: MOV SBUF,#48H
LCALL WAIT_TI
MOV SBUF,#45H
LCALL WAIT_TI
MOV SBUF,#48H
LCALL WAIT_TI
MOV SBUF,#4CH
LCALL WAIT_TI
MOV SBUF,#4CH
LCALL WAIT_TI
MOV SBUF,#4FH
LCALL WAIT_TI
MOV SBUF,#20H
LCALL WAIT_TI
MOV SBUF,#57H
LCALL WAIT_TI
MOV SBUF,#4FH
LCALL WAIT_TI
MOV SBUF,#52H
LCALL WAIT_TI
MOV SBUF,#4CH
LCALL WAIT_TI
MOV SBUF,#4CH
LCALL WAIT_TI
MOV SBUF,#21H
LCALL WAIT_TI
JMP $
;=====================================================================
;功能程序
;=====================================================================
;-----------------------------------------------------------------------------------------------------
;初始化端口配置
;------------------------------------------------------------------------------------------------------
INITIAL_IO:
MOV P0M1,#0FFH
MOV P0M2,#0FFH
MOV P1M1,#0ACH
MOV P1M2,#0CH
RET
;-----------------------------------------------------------------------------------------------
;串口通信
;----------------------------------------------------------------------------------------------
;初始化串口
UART_INIT:
MOV BRGCON,#00H
MOV SCON,#50H // select BRG as UART Baud Rate Gen
MOV SSTAT,#0A0H // separate Rx / Tx interrupts
MOV BRGR0,#70H // setup BRG for 19200 baud @ 7.373MHz internal RC oscillator
MOV BRGR1,#01H
MOV BRGCON,#03H
CLR ESR //关闭接收中断
CLR EST //关闭发送中断
SETB EA //开中断
RET
;----------------------------------------------------------------------------------------------------
WAIT_TI: JBC TI,CLR_TI
JMP WAIT_TI
CLR_TI: RET
END
记得在代码录入的过程中经常存盘。有些语句的意思初学者可能不很明白,随着对本书的学习,语法问题会在今后逐渐学会。本章的目的是介绍开发单片机程序的一般过程,因此,刚开始你只要做到不把代码敲错就可以了。
2、点击图标,进行编译配置设置(由于以后还将介绍具体的配置方法,现在只需要按照图示配置即可)。
此时为默认选项。
按照上图在’Create HEX File’选项前打勾点击确定。这样在编译完成汇编或C语言代码后,将生成一个*.hex文件,否则将不会生成*.hex文件。
3、然后点击工具栏的图标编译所有文件。
4、在命令框内出现如下图所示的内容:
5、此时在你存放项目文件的目录内将生成一个helloworld.hex文件,将这个文件烧入PLC936单片机(烧录的详细过程请参考附录D----手把手教你如何搭建一个单片机程序运行环境),即可实现发送“HELLO WORLD !”给PC的功能。
以上是如何应用keil软件来建立项目、写入源代码、编译、生成hex文件全过程。现在来看看我们代码的运行效果(高手提示我们:任何代码都要测试了才能通过,经不起测试都是垃圾)。
看看我们的运行结果:
1、首先将烧好了的LPC936的串口通信端口和PC机的串口端口相连,然后打开串口调试助手,如下图。
选择波特率:19200
此时,启动LPC936单片机运行。
结果在串口调试助手的接收区却显示出了乱码,而不是出现“hello world!”的字样。
用宋丹丹的话说:太伤自尊了!一本正经搞了半天,最后还是搞出一堆垃圾。在确定了代码没敲错之后,我决定换一款单片机来验证我的程序。可其它单片机(如P89C61X2BA)没有LPC专用的波特率发生器,因此先对程序的初始化串口程序进行修改,不用LPC自带的波特率发生器,将其改为用T1(定时记数器1)作为波特率发生器,也就是将代码:
;----------------------------------------------------------------------------------------------
;初始化串口
UART_INIT:
MOV BRGCON,#00H
MOV SCON,#50H // select BRG as UART Baud Rate Gen
MOV SSTAT,#0A0H // separate Rx / Tx interrupts
MOV BRGR0,#70H // setup BRG for 19200 baud @ 7.373MHz
MOV BRGR1,#01H
MOV BRGCON,#03H
CLR ESR //关闭接收中断
CLR EST //关闭发送中断
SETB EA //开中断
RET
改为:
UART_INIT:
MOV SCON,#50H ;SCON = 0x50; //SCON: serail mode 1, 8-bit UART,
MOV TMOD,#21H ;TMOD = 0x21; //TMOD: timer 1,
MOV PCON,#80H ;电源管理;PCON = 0x80; //SMOD=1
MOV TH1,#244
MOV IE,#90H ;Enable Serial Interrupt //EA=1;ES=1;
SETB TR1
RET
大家看看串口助手得到的效果:
结果还是一样……LPC也太不争气了!
之后我又将同样的用T1产生波特率的程序放在了P89C61X2BA上去运行。则得到了想要的结果:
估计现在有许多人都是不理解的,包括我在内。怎么会出现这种现象呢?经过多次的分析和思考,以及阅读官方提供的资料,始终没有解决这个问题,我开始估计是LPC936单片机的串口端口在设计上有某种缺陷,或是有些资料自己没有理解透彻。但是我们不能因为小小的挫折就放弃用LPC936实现hello world!的显示这一目的,因此我们要另想办法解决。
硬的不行,只有来软的了。现在我决定用两条I/O口线,模拟UART串口发送数据给PC,源代码如下:
P1M1 EQU 91H
P1M2 EQU 92H
;===================================================
;主程序
ORG 0000H
LJMP MAIN
ORG 0080H
MAIN: MOV SP,#10H
MOV P1M1,#00H
MOV P1M2,#01H
SETB P1.0
LCALL DELAY
LCALL DELAY
LCALL DELAY
MOV A,#48H
LCALL SEND_UART_BYTE
MOV A,#45H
LCALL SEND_UART_BYTE
MOV A,#4CH
LCALL SEND_UART_BYTE
MOV A,#4CH
LCALL SEND_UART_BYTE
MOV A,#4FH
LCALL SEND_UART_BYTE
MOV A,#20H
LCALL SEND_UART_BYTE
MOV A,#57H
LCALL SEND_UART_BYTE
MOV A,#4FH
LCALL SEND_UART_BYTE
MOV A,#52H
LCALL SEND_UART_BYTE
MOV A,#4CH
LCALL SEND_UART_BYTE
MOV A,#44H
LCALL SEND_UART_BYTE
MOV A,#21H
LCALL SEND_UART_BYTE
JMP $
;===========================================================
;发送字节
SEND_UART_BYTE:
CLR P1.0 ;发送起始位
LCALL DELAY
MOV C,ACC.0
MOV P1.0,C
LCALL DELAY
MOV C,ACC.1
MOV P1.0,C
LCALL DELAY
MOV C,ACC.2
MOV P1.0,C
LCALL DELAY
MOV C,ACC.3
MOV P1.0,C
LCALL DELAY
MOV C,ACC.4
MOV P1.0,C
LCALL DELAY
MOV C,ACC.5
MOV P1.0,C
LCALL DELAY
MOV C,ACC.6
MOV P1.0,C
LCALL DELAY
MOV C,ACC.7
MOV P1.0,C
LCALL DELAY
SETB P1.0 ;发送停止位
LCALL DELAY
RET
;============================================================
;延时(决定波特率)
DELAY: PUSH ACC
MOV R0,#90
DELAY1:
DJNZ R0,DELAY1
POP ACC
RET
END
按照前面所述的方法,将上面的源代码编译烧写之后,在串口调试助手内,将得到你所希望看到的hello world!的字样!(终于成功了……)
现在都什么年代了,这家伙怎么还在用石器时代的东西写程序?看到这里,有些读者心里要问了。下一章,我就用C语言来写同样的程序,也让大家看看,什么叫做高层次。