八、mini2440裸机程序之UART(2)UART0与PC串口通信【转】
转自:http://blog.csdn.net/shengnan_wu/article/details/8309417
版权声明:本文为博主原创文章,未经博主允许不得转载。
1.相关原理图
2.相关寄存器介绍与配置
1)与管脚相关
①GPHCON
注:
配置成UART0功能:
GPHCON |= 10 10 10 10b(还要在GPHUP里把管脚禁止上拉)
2)与中断相关
上传图片好麻烦,跟以前相关的这里就不贴了
3)与UART0相关
①ULCON0
注:
Infrared Mode :选择普通模式
Parity Mode : 选择不执行奇偶校验
Number of stop :选择1个结束位
Word length :8-bits字长
ULCON0 = 0x03
②UBRDIV0和UCON0
注:
UBRDIVn = (int)(UART clock /(baud rate * 16)) - 1,因为下面我们选择了PCLK作为UARTclock。所以UBRDIV0=(int)PCLK/(baudrate*16)) - 1。其中PCLK=101.25MHz,波特率选择115200
UBRDIV0=(int)( 101250000/(115200*16) ) - 1 = 54.
选择UBRDIV0= 0x36
注:
Clock selection :时钟源选择,选择PCLK,根据上个定时器实验得知PCLK使用101.25MHz。00b
注:
脉冲触发:指的是在Tx buffer从有数据跳变为空时(对于接收模式来说,则是Rx buffer从空跳变为有数据)触发一次边沿中断,之后即使为空,但是没有这个跳变是不会触发中断的(Non-FIFO)。对于使用FIFO来说,一旦到达Tx/Rx FIFO的触发水平时才产生一个边沿中断,其它时候都不会触发中断。
水平触发:指的是只要Tx buffer为空(对于接收模式来说,则是只要Rx buffer有数据)则一直产生中断请求 (电平中断)。对于使用FIFO来说,只要是高于(接收模式)/低于(发送模式)或等于触发水平就一直产生中断(电平中断)。
①如果发送中断采用了脉冲触发,在发送中断处理里执行太长时间的情况下,导致下个脉冲中断请求在中断处理函数里产生了,那么如果请求中断请求位是在后面清除的,则会引起下个脉冲中断请求被清除了而无法再次进入发送中断(因为数据已经被发送出去了,则不会再达到触发水平(FIFO)或者从从有数据到空的跳变(non-FIFO))。而如果是采用水平触发则不会发生这种情况,因为只要是空的(non-FIFO)或低于等于触发水平(FIFO),即便被清除了请求位,还是会产生请求,但是这样会连绵不断的触发中断。
②如果接收中断采用了脉冲触发,在接收中断处理里执行太长时间的情况下,也会导致下一个脉冲中断请求在中断处理函数里产生了,那么如果请求中断请求位是在后面清除的,则会引起下个脉冲中断请求被清除了而无法再次进入发送中断(因为之前的数据还没被取出,则不会再达到触发水平(FIFO)或者从空到有数据的跳变(non-FIFO))。而如果是采用水平触发则不会发生这种情况,因为只要有数据(non-FIFO)或高于等于触发水平(FIFO),即便被清除了请求位,还是会产生请求,但是这样会连绵不断的触发中断。
举个栗子:
①只使能发送中断,选择FIFO,触发水平为empty,在发送中断里发送一个字节,延迟3秒,这段时间足够让Tx FIFO里从一个数据跳变到empty(2),即发送中断请求(1)在中断处理函数里产生了。
·使用脉冲触发:退出中断处理函数清除了中断请求,也就把(1)给清掉了,进不了发送中断无法给数据,并且Tx FIFO一直为空(无法出现(2)) , 因此不再产生中断。
·使用水平触发:退出中断处理函数清除了中断请求,也把(1)给清掉了,但是只要是低于或等于empty在FIFO里,则电平中断还会产生,能够再次进入中断。
②只使能接收中断,选择FIFO,触发水平为1-byte,PC机上发一个字节触发中断,在接收中断里接收一个字节,延迟3秒,在这段时间里再发1个字节,这样会有一个从empty到1-byte的跳变(2),这样就在中断服务函数里产生了中断请求,即接收中断请求(1)在中断处理函数里产生了。
·使用脉冲触发:退出中断处理函数清除了中断请求,也就把(1)给清掉了,进不了接收中断无法取出数据,并且Rx FIFO一直1-byte数据(无法出现(2)) ,因此不再产生中断。
·使用水平触发:退出中断处理函数清除了中断请求,也把(1)给清掉了,但是只要是高于或等于1-byte数据在FIFO里,则电平中断还会产生,能够再次进入中断。
经测试成立。
Tx interrupt type :保险点选择水平触发就行了,不过这个我们只在需要发送数据再打开,发送完数据再关闭,这样就防止老是进入中断。1b
Rx interrupt type :同理 ,选择水平触发就好了,只要有数据就进入中断处理数据。1b
Rx timeout enable :选择禁止。0b
Rx error status interrupt enable :禁止接收错误状态中断。0b
Loopback mode :选择普通模式。0b
Send break signal :选择正常传输。0b
注:
Transmit mode :选择中断请求或者轮询模式。01b
Receive mode :选择中断模式或者轮询模式。01b
UCON0 = 0x305
③UFCON0
注:
Tx FIFO trigger level :选择emply,只有在FIFO到达空了才会触发中断。00b
Rx FIFO trigger level :选择1-bype,这样只要接收到1个字节就直接进入中断取出处理。00b
Tx FIFO reset :选择normal。0b
Rx FIFO reset :选择normal。0b
FIFO enable :使能FIFO。1b
UFCON0 = 0x01
④UMCON0
注:
AFC flow control:不使能自动流控。0b
Request to send:不使能自动流控,这个位也不需要用到
初始化时:UMCON0 = 0;
⑤UTRSTAT0
注:只读
Transmitter empty:当发送缓冲寄存器(FIFO)已经没有有效数据可以发送并且发送数据移位器为空。该域会被置为‘1’。
Transmit buffser empty:当发送缓冲寄存器空时被置为‘1’。(只针对于Non-FIFO,Tx FIFO模式需要检测UFSTAT寄存器的count 位和Tx FIFO Full 位)
Receive buffer data ready:当接收缓冲寄存器包含了从PXDn端口接收到的有效数据时被设置为‘1’。(只针对于Non-FIFO,Rx FIFO模式需要检测UFSTAT寄存器的count位和Rx FIFO Full 位)
⑥UERSTAT0
注:(当这个状态寄存器被读取时会自动清零)
Break detect:当一个break 信号被接收到了这个位会被置‘1’。
Frame Error :当一个帧错误在接收操作期间产生时被置‘1’。
Parity error :当一个奇偶校验错误在接收期间产生时被置‘1’。
Overrun error:当一个overrun错误在接收期间产生时被置‘1’。
⑦UFSTAT0
注:
Tx FIFO full :当发送FIFO在发送期间满了,这个位会被置‘1’。
Tx FIFO count :Tx FIFO里的data数。
Rx FIFO Full :当接收FIFO在接收期间满了,这个位会被置‘1’。
Rx FIFO count :Rx FIFO里的data数。
⑧UMSTAT0
注:
Delta CTS:指明了nCTS输入到S3C2440A状态是否改变了知道该位被CPU读取了才自动清零。
Clear to send:要发送数据时需要检查该位是否有效,有效才可以写数据到Tx FIFO。
这两个都用在自动流控功能里。如果双方都不使用自动流控。这个也可以不管。
⑨UTXH0(UART0发送缓冲寄存器)
注:
TXDATA0:存放用户要发送的数据
⑩URXH0(UART0接收缓冲寄存器)
注:
(当overrun error发生时,URXH0必须被读出,否则,下一个接收到的数据也会造成overrun error尽管UERSTAT0的overrun位已经被清除了)
RXDATA:存放接收到的数据
3.流程图设计
①主流程图
②中断处理子程序
③中断服务子函数
4.程序设计
①Makefile
uart0.bin : head.o demoUart0.o
arm-linux-ld -Tuart0.lds -o uart0_elf $^
arm-linux-objcopy -O binary -S uart0_elf $@
arm-linux-objdump -D -m arm uart0_elf > uart0.dis
%.o : %.S
arm-linux-gcc -Wall -c -o $@ $<
%.o : %.c
arm-linux-gcc -Wall -c -o $@ $<
clean:
rm -f uart0.dis uart0.bin uart0_elf *.o *.bak
/****************************************************************************************************************************/
②uart0.lds
SECTIONS {
first 0x00000000 : { head.o }
second 0x30000000 : AT(2048) { demoUart0.o }
}
/*****************************************************************************************************************************/
③head.S
@与内存相关
.equ BWSCON , 0x48000000
.equ BANKCON6 , 0x4800001C
.equ REFRESH , 0x48000024
.equ BANKSIZE , 0x48000028
.equ MRSRB6 , 0x4800002C
@与中断相关
.equ INTMSK , 0x4A000008
.equ INTSUBMSK , 0x4A00001C
.equ SUBSRCPND , 0x4A000018
.equ SRCPND , 0x4A000000
.equ INTPND , 0x4A000010
@与看门狗相关
.equ WTCON , 0x53000000
@与管脚配置相关的
.equ GPHCON , 0x56000070
.equ GPHUP , 0x56000078
@与UART0配置相关的
.equ ULCON0 , 0x50000000
.equ UBRDIV0 , 0x50000028
.equ UCON0 , 0x50000004
.equ UFCON0 , 0x50000008
.equ UMCON0 , 0x5000000C
.equ UTRSTAT0 , 0x50000010 @read-only
.equ UERSTAT0 , 0x50000014
.equ UFSTAT0 , 0x50000018
.equ UMSTAT0 , 0x5000001C
.equ UTXH0 , 0x50000020 @little-endian
.equ URXH0 , 0x50000024 @little-endian
@时钟相关寄存器
.equ MPLLCON , 0x4C000004
.equ UPLLCON , 0x4C000008
.equ CLKDIVN , 0x4C000014
.equ CAMDIVN , 0x4C000018
@与灯光配置相关的
.equ GPBCON , 0x56000010
.equ GPBDAT , 0x56000014
.text
.global _start
_start:
/***********设置中断向量表*************/
b ResetInit @复位异常入口
HandlerUndef:
b HandlerUndef @未定义异常入口
HandlerSWI:
b HandlerSWI @软中断异常入口
HandlerPabort:
b HandlerPabort @取指中止异常入口
HandlerDabort:
b HandlerDabort @数据中止异常入口
HandlerNotUsed:
b HandlerNotUsed @保留
b HandlerIRQ @中断异常入口
HandlerFIQ:
b HandlerFIQ @快中断异常入口
/************END设置中断向量表***********/
ResetInit:
/*************关闭看门狗****************/
ldr r0 , =WTCON
mov r1 , #0x0
str r1 , [r0]
/************END关闭看门狗**************/
/**********初始化LED灯管脚************
@把LED1-4管脚置为输出
ldr r0 , =GPBCON
ldr r1 , [r0] @把GPBCON里的内容加载到r1里
ldr r2 , =(0xFF<<10)
bic r1 , r1 ,r2 @操作数取反码或上r1,用于清零工作
ldr r2 , =(0x55<<10)
orr r1 , r1 , r2
str r1 , [r0]
ldr r0 , =GPBDAT
ldr r1 , [r0]
ldr r2 , =(0x0F<<5)
bic r1 , r1 , r2
orr r1 , r1 , r2
str r1 , [r0]
***********END***************/
/**********初始化相关管脚为UART0功能************/
ldr r0 , =GPHCON
ldr r1 , [r0] @把GPHCON里的内容加载到r1里
ldr r2 , =0xFF
bic r1 , r1 ,r2 @操作数取反码&r1,用于清零
ldr r2 , =0xAA
orr r1 , r1 , r2
str r1 , [r0]
ldr r0 , =GPHUP
ldr r1 , [r0]
bic r1 , r1 , #0x0f
orr r1 , r1 , #0x0f
str r1 , [r0] @管脚禁止上拉功能
/***********END***************/
/***********配置时钟相关寄存器***********/
ldr lr , =Memconf
ldr pc , =ClkConfigure
/**********END配置时钟相关寄存器*********/
/***********配置内存相关寄存器***********/
Memconf:
ldr lr , =InitSystemSp
ldr pc , =MemConfigure
/**********END配置内存相关寄存器*********/
/***********设置系统模式下的sp***********/
InitSystemSp:
@复位默认进入系统模式
ldr sp , =0x34000000
/********END设置系统模式下的sp***********/
/**********配置UART0相关寄存器***********/
ldr lr , =CopyToSdram
ldr pc , =Uart0Configure
/*******************END******************/
/************拷贝中断处理子函数到内存****/
CopyToSdram:
ldr lr , =Uart0IntConf
ldr pc , =copy_bootsram_to_sdram
/*******************END******************/
/********配置UART0中断相关寄存器*******/
Uart0IntConf:
ldr r0 , =INTMSK @使能UART0中断
ldr r1 , [r0]
bic r1 , r1 , #(0x01<<28)
str r1 , [r0]
ldr r0 , =INTSUBMSK @禁止INT_ERR0(1<<2),
ldr r1 , [r0] @使能INT_TXD0(0<<1),INT_RXD0(0<<0),中断
bic r1 , r1 , #0x07
orr r1 , r1 , #0x06
str r1 , [r0]
/******END配置UART0中断相关寄存器******/
/*进入中断模式设置中断模式下的sp退出到系统模式
*使能IRQ中断*/
msr cpsr_c , 0xd2 @进入中断模式,禁止中断,其中cpsr后的_c表示cpsr[7:0]
ldr sp , =0x33500000 @设置sp
msr cpsr_c , 0x5f @退出到系统模式,使能IRQ中断
/*END进入中断模式设置中断模式下的sp退出到系统模式*/
/*************等待中断******************/
halt_loop:
b halt_loop
/*********************END****************/
/*************IRQ中断服务子程序**********/
HandlerIRQ:
@因为在产生中断异常时,lr存的是当前指令的下一条指令,所以要减四
sub lr , lr , #4
@把相关寄存器压入中断模式下的栈
@db表示sp每次传送内容前减1
stmdb sp! , {r0-r12 , lr}
@禁止IRQ中断
mrs r1 , cpsr_all
orr r1 , r1 , #(1<<7)
msr cpsr_all , r1
@调用中断服务处理函数
ldr lr , =IRQ_Return
ldr pc , =main
IRQ_Return:
/* ldr r0 , =GPBDAT
ldr r1 , [r0]
ldr r2 , =(0x0F<<5)
bic r1 , r1 , r2
orr r1 , r1 , r2
str r1 , [r0]*/
@把栈里面的内容推出到相应寄存器里,并把lr推到pc寄存器实现跳转
@ia表示每次传送后加1 , 当寄存器列表中包含了pc寄存器,选用^为后缀,就会把spsr拷贝到cpsr
ldmia sp! , {r0-r12 , pc}^
/********END IRQ中断服务子程序***********/
/*配置Uart0相关寄存器*/
Uart0Configure:
ldr r0 , =ULCON0
mov r1 , #0x03
str r1 , [r0] @普通模式,禁止奇偶校验,1个结束位,8-bit字长
ldr r0 , =UBRDIV0
mov r1 , #0x36
str r1 , [r0] @波特率选择115200,所以UBRDIV0=54
ldr r0 , =UCON0
ldr r1 , =0x305
str r1 , [r0] @时钟源=PCLK
@Rx,Tx水平触发,禁止接收超时中断,禁止接收错误状态中断,
@不使用回路模式,不发送break信号
@中断方式发送接收数据到缓冲寄存器
ldr r0 , =UFCON0
mov r1 ,#0x01
str r1 , [r0] @Tx FIFO触发水平选择空时触发,
@Rx FIFO触发水平选择>=1字节触发
@使能FIFO
ldr r0 , =UMCON0
mov r1 , #0x0
str r1 , [r0] @禁止自动流控功能。
bx lr
/******************END******************/
/***********配置时钟相关寄存器***********/
ClkConfigure:
@UPLL选择MDIV=0x38,PDIV=2,SDIV=1.得UPLL clock=96MHz
@这里先设置UPLL是为了与设置MPLL隔开至少7个NOP的时间间隔
ldr r0 , =UPLLCON
ldr r1 , =(0x38<<12)|(0x02<<4)|(0x01<<0)
str r1 , [r0]
@分频比FCLK:HCLK:PCLK=1:4:4 , UCLK=UPLL_clock/2=48MHz
ldr r0 , =CLKDIVN
mov r1 , #(0x01<<3)|(0x02<<1)|(0x00<<0)
str r1 , [r0]
ldr r0 , =CAMDIVN
ldr r1 , [r0]
bic r1 , r1 , #(0x03<<8)
str r1 , [r0]
@因为HDIVN不是0 , 所以CPU总线模式要从高速总线模式改变
@为异步总线模式
mrc p15 , 0 , r1 , c1 , c0 , 0
orr r0 , r0 , #0xC0000000
mcr p15 , 0 , r0 , c1 , c0 , 0
@MPLL选择MDIV=0x7f,PDIV=2,SDIV=1,得MPLL clock=405MHz
ldr r0 , =MPLLCON
ldr r1 , =(0x7f<<12)|(0x02<<4)|(0x01<<0)
str r1 , [r0]
bx lr
/***************END*************************/
/*******内存初始化子程序*********/
MemConfigure:
@BWSCON[27:24] = 0 0 10B
ldr r0 , =BWSCON
ldr r1 , [r0]
ldr r2 , =(0x0F<<24)
bic r1 , r1 , r2
ldr r2 , =(0x02<<24)
orr r1 , r1 , r2
str r1 , [r0]
@BANKCON6[16:15]=11B,BANKCON6[3:0]=00 01B
ldr r0 , =BANKCON6
ldr r1 , [r0]
ldr r2 , =(0x03<<15)
bic r1 , r1 , r2
orr r1 , r1 , r2
ldr r2 , =0x0F
bic r1 , r1 , r2
ldr r2 , = 0x01
orr r1 , r1 , r2
str r1 , [r0]
@这里的Trp要大于20ns , Trc要大于70ns,HCLK=101.25MHz
@故时钟周期=1s/HCLK=9.8ns,Trp*9.8>20ns ==> Trp>=3 ==> Trp域=01b
@Trp*9.8+Tsrc*9.8>70ns ==> Tsrc>=5 ==> Tsrc域=01b
@REFRESH[23:18] = 1 0 01 01B,REFRESH[10:0] = 0x4E8
ldr r0 , =REFRESH
ldr r1 , [r0]
ldr r2 , =(0x3F<<18)
bic r1 , r1 , r2
ldr r2 , =(0x25<<18)
orr r1 , r1 , r2
ldr r2 , =0x7FF
bic r1 , r1 , r2
ldr r2 , =0x4E9
orr r1 , r1 , r2
str r1 , [r0]
@BANKSIZE[7:0] = 1 0 1 1 0 001 B
ldr r0 , =BANKSIZE
ldr r1 , [r0]
ldr r2 , =0xFF
bic r1 , r1 , r2
ldr r2 , =0xB1
orr r1 , r1 , r2
str r1 , [r0]
@MRSRB6[11:0] = 0 00 011 0 000 B
ldr r0 , =MRSRB6
ldr r1 , [r0]
ldr r2 , =0x3FF
bic r1 , r1 , r2
ldr r2 , =0x030
orr r1 , r1 , r2
str r1 , [r0]
bx lr @函数返回
/******END内存初始化子程序*******/
/******拷贝后2048到4096之间的代码到sdram*******/
copy_bootsram_to_sdram:
ldr r0 , =0x800
ldr r1 , =0x30000000
ldr r2 , =0x1000
copy:
ldr r3 , [r0] , #4
str r3 , [r1] , #4
cmp r0 , r2
bne copy
bx lr
/*****END拷贝前4K代码到sdram*****/
/****************************************************************************************************************/
④demoUart0.h
#ifndef DEMOUART0_H
#define DEMOUART0_H
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define SRCPND (*(volatile unsigned long *)0x4A000000)
#define INTPND (*(volatile unsigned long *)0x4A000010)
#define SUBSRCPND (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned long *)0x4A00001C)
#define UTXH0 (*(volatile unsigned long *)0x50000020)
#define URXH0 (*(volatile unsigned long *)0x50000024)
#define UFSTAT0 (*(volatile unsigned long *)0x50000018)
#define UMCON0 (*(volatile unsigned long *)0x5000000C)
#define INT_TXD0 (0x01<<1)
#define INT_RXD0 (0x01)
#define TFIFO_FULL (0x01<<14)
#define RFIFO_DATA_NUM (0x1f)
/*消息队列大小*/
#define QUEUE_SIZE 100
unsigned char p[QUEUE_SIZE];
/*
*选择了水平触发所以这里可以直接把所以中断请求清除,
*退出之后只要满足触发条件还是会触发中断
**/
#define cleanIQR() \
do {\
SUBSRCPND = SUBSRCPND;\
SRCPND = SRCPND;\
INTPND = INTPND;\
} while(0)
/*接收数据的消息队列结构体*/
typedef struct MSG_QUEUE
{
unsigned char *Qmsg;
unsigned char *write;
unsigned char *read;
unsigned char size;
} MSG_QUEUE;
/***************函数声明********************/
/*把消息队列qmsg里的消息通过uart0发送出去*/
void uart0_sent_msg(MSG_QUEUE *qmsg);
/*把接收到的消息存入到qmsg消息队列里*/
void uart0_revice_msg(MSG_QUEUE *qmsg);
/*延时函数*/
void delay(volatile unsigned long second);
/*******************************************/
/*********************函数定义************************/
void delay(volatile unsigned long second)
{
volatile unsigned long i;
while(second--){
i=1000000;
while(--i);
}
}
void uart0_sent_msg(MSG_QUEUE *qmsg)
{
while( !(UFSTAT0&TFIFO_FULL) && qmsg->size ) {
/*如果Tx FIFO还被满,并且队列还有数据要发送,则一直循环填入UTXH0*/
UTXH0 = *qmsg->read++;
--qmsg->size;
if(qmsg->read == (qmsg->Qmsg+QUEUE_SIZE-1) ){
qmsg->read = qmsg->Qmsg;
}
}
return ;
}
void uart0_revice_msg(MSG_QUEUE *qmsg)
{
while( (UFSTAT0 & RFIFO_DATA_NUM) && (qmsg->size < QUEUE_SIZE) ){
/*如果接收FIFO里还有数据并且消息队列uart0_qmsg里数据没满,则继续取出数据*/
*qmsg->write++ = (volatile unsigned char)URXH0;
++qmsg->size;
if(qmsg->write == (qmsg->Qmsg+QUEUE_SIZE-1) ) {
qmsg->write = qmsg->Qmsg;
}
}
return;
}
/******************************************/
#endif
/***********************************************************************************************************************/
⑤demoUart0.c
#include "demoUart0.h"
int main()
{
static unsigned char i;
static MSG_QUEUE *uart0_qmsg;
if( INTOFFSET != 28 ){
cleanIQR();
return 0;
}
/*第一次进来初始化队列*/
if(i==0){
uart0_qmsg->Qmsg = p;
uart0_qmsg->write = p;
uart0_qmsg->read = p;
uart0_qmsg->size=0;
i = 1;
}
/*接收中断处理*/
if(SUBSRCPND&INT_RXD0){
uart0_revice_msg(uart0_qmsg);
/*如果消息队列里有数据,则使能INT_TXD0中断*/
if(uart0_qmsg->size){
INTSUBMSK &= ~(1<<1);
}
cleanIQR();
return 0;
}
/*发送中断处理*/
if(SUBSRCPND&INT_TXD0){
uart0_sent_msg(uart0_qmsg);
/*关闭INT_TXD0中断使能*/
INTSUBMSK |= (1<<1);
cleanIQR();
return 0;
}
/*选择了水平触发所以这里可以直接把所以中断请求清除,退出之后只要满足触发条件还是会触发中断*/
cleanIQR();
return 0;
}