一、串口通信原理
UART:Universal Asynchronous Receiver/Transmitter(通用异步收发送器),用来传输串行数据,发送数据时,CPU将并行数据写入UART,UART按照一定格式在TxD线上串行发出;接收数据时,UART检测到RxD线上的信号,将串行收集放到缓冲区中,CPU即可读取UART获得的这些数据。
UART最精简的连线形式只有3根线,TXD用于发送,RXD用于接收,GND用于提供参考电平。UART之间以帧作为数据传输单位,帧由具有完整意义的若干位组成,它包含开始位、数据位、校验位和停止位。发送数据之前,互相通信的UART之间要约定好数据传输速率(波特率的倒数)、数据的传输格式(多少个数据位、是否使用校验位、奇校验还是偶校验、多少个停止位)。
二、S3C2440串口介绍
s3c2440提供了三个UART端口,它们都可以通过查询、中断和DMA方式传输数据,而且每个UART都分别有一个64个字节的接收FIFO和一个64个字节的发送FIFO。如下图所示:每个UART包含一个波特率发生器、发送器、接收器和一个控制单元。波特率发生器可以由PCLK、FCLK/n或UEXTCLK(外部输入时钟)时钟驱动。UART通过使用系统时钟可以支持最高115.2Kbps的比特率。如果是使用外部器件提供UEXTCLK的UART,则UART可以运行在更高的速度。发送器和接收器各包含一个64字节的FIFO和数据移位器。要发送数据时,先将数据写入到FIFO接着在发送前复制到发送移位器中,随后将数据从发送数据引脚(TXDn)移出;接收数据时,从接收数据引脚(RXDn)移入收到的数据,接着从移位器复制到FIFO。
三、S3C2440的串口寄存器配置
3.1 串行数据格式设置
例子:ULCON0=0x03 //8N1(正常模式 8个数据位,无较验,1个停止位)
3.2 UART控制寄存器
例子:UCON=0x05 //时钟使用PLCK,中断为边沿触发,禁止接受和发送超时,不产生接受错误中断,不采用RX、TX直接相连的调节模式,rx和tx处于中断或者查询模式。
3.3 UART发送接受状态寄存器
当程序使用的是查询方式收发数据时,必须通过读取UART寄存器的值来正确控制UART数据的收发。
#define TXD0READY (1<<2)
#define RXD0READY (1)
//发送字符
void putc(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT0 & TXD0READY));
/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
UTXH0 = c;
}
//接受字符
unsigned char getc(void)
{
/* 等待,直到接收缓冲区中的有数据 */
while (!(UTRSTAT0 & RXD0READY));
/* 直接读取URXH0寄存器,即可获得接收到的数据 */
return URXH0;
}
当串口有数据可读时,程序直接读取URXHn,当用户想发送数据时直接将数据写入到UTXHn即可。
3.5 波特率寄存器
UBRDIVn由下列表达式确定:
UBRDIVn=(int)(UART时钟/(波特率*16))-1
假如uart0采用中断模式来收发数据我们必须要做三件事:
1.在程序的启动代码中开中断
2.设置INTMSK和INTSUBMSK,打开uart0中断
3.配置GPH2,GPH3用作TXD0,RXD0
四、串口中断控制led灯实例(tq2440)
启动代码:初始化时钟、led引脚配置、uart0初始化以及中断相关设定
.extern main
.text
.global _start
_start:
@******************************************************************************
@ 异常向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中断模式的向量地址
b HandleIRQ
@ 0x1c: 快中断模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
msr cpsr_c, #0xd2 @ 进入中断模式
ldr sp, =3072 @ 设置中断模式栈指针
msr cpsr_c, #0xd3 @ 进入管理模式
ldr sp, =4096 @ 设置管理模式栈指针,
@ 其实复位之后,CPU就处于管理模式,
@ 前面的“ldr sp, =4096”完成同样的功能,此句可省略
bl clock_init
bl init_led @ 初始化LED的GPIO管脚
bl init_irq @ 调用中断初始化函数,在init.c中
msr cpsr_c, #0x53 @ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 计算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此时的sp是中断模式的sp
@ 初始值是上面设置的3072
ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =EINT_Handle @ 调用中断服务函数,在interrupt.c中
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
启动代码中使用到的初始化代码
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))
#define GPB5_msk (3<<(5*2))
#define GPB6_msk (3<<(6*2))
#define GPB7_msk (3<<(7*2))
#define GPB8_msk (3<<(8*2))
/*
* K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
*/
#define GPF0_int (0x2<<(0*2))
#define GPF1_int (0x2<<(1*2))
#define GPF2_int (0x2<<(2*2))
#define GPF4_int (0x2<<(4*2))
#define GPF0_msk (3<<(0*2))
#define GPF1_msk (3<<(1*2))
#define GPF2_msk (3<<(2*2))
#define GPF4_msk (3<<(4*2))
/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
void init_led(void)
{
// LED1,LED2,LED3,LED4对应的4根引脚设为输出
GPBCON &= ~(GPB5_msk | GPB6_msk | GPB7_msk | GPB8_msk);
GPBCON |= GPB5_out | GPB6_out | GPB7_out | GPB8_out;
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
INTMSK &=~(1<<28); //开启uart0中断
INTSUBMSK &=~(3<<0);//开启uart0的接收和发送中断
GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
GPHUP = 0x0c; // GPH2,GPH3内部上拉
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = 0x1a; // 波特率为115200
/*
* 设定优先级:
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ2 > REQ3,即EINT0 > EINT1 > EINT2
* 仲裁器1、6无需设置
* 最终:
* EINT0 > EINT1> EINT2 > EINT4 即K4 > K1 > K3 > K2
*/
PRIORITY = (PRIORITY & ((~0x01) | ~(0x3<<7)));
// EINT0、EINT1、EINT2、EINT4_7使能
INTMSK &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
}
/*
* 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
* 有如下计算公式:
* S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)
* S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
* 其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
* 对于本开发板,Fin = 12MHz
* 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4,
* FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
*/
void clock_init(void)
{
CLKDIVN = 0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000\n" /* 设置为“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0\n" /* 写入控制寄存器 */
);
MPLLCON =((0x5c<<12)|(0x01<<4)|(0x02)); /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
中断处理函数
#include "s3c24xx.h"
void EINT_Handle()
{
/*
* K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
* 即 EINT1, ETIN4, EINT2, EINT0
* oft为 1, 4, 2, 0 (对应INTMSK寄存器)
*/
switch(URXH0)
{
// K1被按下
case '1':
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<5); // LED1点亮
break;
}
// K2被按下
case '2':
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<6); // LED2点亮
break;
}
// K3被按下
case '3':
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<7); // LED3点亮
break;
}
// K4被按下
case '4':
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<8); // LED4点亮
break;
}
default:
break;
}
//清中断
SUBSRCPND = 3<<0;
SRCPND = 1<<28;
INTPND = 1<<28;
}
主函数
int main()
{
while(1);
return 0;
}
完整源码地址: http://pan.baidu.com/share/link?shareid=3888282204&uk=101680913
参考文档:http://www.cnblogs.com/idle_man/archive/2010/12/19/1910548.html