UART(一)裸机编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/83025000

S5PV210 包含 4 个异步收发器(UART),提供 4 个独立的异步串行输入/输出(I/O)端口。所有端口可工作于中断模式或 DMA 模式。提供高达 3Mbps 的位速率。每个 UART 包含 2 个 FIFO 用于接收和发送数据。具有可编程的波特率、红外收发、1 位或 2 位停止位、5~8 位数据位、校验。

其中UART1和UART2也被转为232接口
S5PV210的uart结构图如下

数据发送:要发送的数据帧是可编程的。它包含 1 位起始位, 5~8 位数据位, 1 个可选校验位, 1
或 2 位停止位,这些都通过 ULCONn 寄存器来设置。在 FIFO 模式下发送器将要发送的数据发送给 Tx FIFO,
在非 FIFO 模式下,发送器将要发送的数据发送给 Tx 保持寄存器。
数据接收:和数据发送类似。

串口编程操作步骤如下
1、配置时钟,选择时钟源。
2、配置ULCONn寄存器,设置模式,校验位,停止位和数据位
3、配置UCONn寄存器,设置数据接收和发送模式,以及时钟源等
4、配置UFCONn寄存器,启用或禁用FIFO
5、配置UBRDIVnUDIVSLOTn寄存器:计算波特率
6、发送数据UTXHn:查询状态寄存器UTRSTATn等待发送器为空,将要发送的8位数据赋给发送缓存寄存器UTXHn
7、接收数据URXHn:查询状态寄存器UTRSTATn等待接收缓冲区有数据可读,从接收缓存寄存器URXHn中取出数据

波特率计算

计算出来的小数部分需要进行查表

start.S

.global _start				/* 声明一个全局的标号 */
_start:
		bl uart_init		/* 串口初始化 */
		bl main			/* 跳转到C函数去执行 */
halt:
		b halt                  /* 死循环 */

uart.c

#define GPA0CON		*((volatile unsigned int *)0xE0200000)
#define ULCON0 		*((volatile unsigned int *)0xE2900000)
#define UCON0 		*((volatile unsigned int *)0xE2900004)
#define UFCON0 		*((volatile unsigned int *)0xE2900008)
#define UTRSTAT0 	*((volatile unsigned int *)0xE2900010)
#define UTXH0  		*((volatile unsigned int *)0xE2900020)
#define URXH0 		*((volatile unsigned int *)0xE2900024)
#define UBRDIV0 	*((volatile unsigned int *)0xE2900028)
#define UDIVSLOT0	*((volatile unsigned int *)0xE290002C)

/* UART0初始化 */
void uart_init()
{
	/*
	** 配置GPA0_0为UART_0_RXD
	** 配置GPA0_1为UART_0_TXD
	*/
	GPA0CON &= ~0xFF;
	GPA0CON |= 0x22;

	/* 8-bits/One stop bit/No parity/Normal mode operation */
	ULCON0 = 0x3 | (0 << 2) | (0 << 3) | (0 << 6);

	/* Interrupt request or polling mode/Normal transmit/Normal operation/PCLK/*/
	UCON0 = 1 | (1 << 2) | (0 << 10);

	/* 静止FIFO */
	UFCON0 = 0;

	/*
	** 波特率计算:115200bps
	** PCLK = 66MHz
	** DIV_VAL = (66000000/(115200 x 16))-1 = 35.8 - 1 = 34.8
	** UBRDIV0 = 34(DIV_VAL的整数部分)
	** (num of 1's in UDIVSLOTn)/16 = 0.8
	** (num of 1's in UDIVSLOTn) = 12
	** UDIVSLOT0 = 0xDDDD (查表)
	*/
	UBRDIV0 = 34;
	UDIVSLOT0 = 0xDDDD;
}

static void uart_send_byte(unsigned char byte)
{
	while (!(UTRSTAT0 & (1 << 2)));	/* 等待发送缓冲区为空 */
	UTXH0 = byte;			/* 发送一字节数据 */		
}

static unsigned char uart_recv_byte()
{
	while (!(UTRSTAT0 & 1));		/* 等待接收缓冲区有数据可读 */
	return URXH0;				/* 接收一字节数据 */		
}

void putchar(int c)
{
	uart_send_byte(c);
	/* 如果只写'\n',只是换行,而不会跳到下一行开头 */
	if (c == '\n')
		uart_send_byte('\r');
}

int getchar()
{
	int c;
	c = uart_recv_byte();
	return c;
}

void puts(char *str)
{
	char *p = str;
	while (*p)
		putchar(*p++);
	putchar('\n');
}

main.c

#define GPC0CON		*((volatile unsigned int *)0xE0200060)
#define GPC0DAT		*((volatile unsigned int *)0xE0200064)

int main()
{
	int c;

	GPC0CON &= ~(0xFF << 12);
	GPC0CON |= 0x11 << 12;		// 配置GPC0_3和GPC0_4为输出
	GPC0DAT &= ~(0x3 << 3);		// 熄灭LED1和LED2

	puts("UART Test in S5PV210");
	puts("1.LED1 Toggle");
	puts("2.LED2 Toggle");
	puts("Please select 1 or 2 to Toggle the LED");
	
	while (1)
	{
		c = getchar();			// 从串口终端获取一个字符
		putchar(c);			// 回显
		putchar('\r');

		if (c == '1')
			GPC0DAT ^= 1 << 3;	// 改变LED1的状态
		else if (c == '2')
			GPC0DAT ^= 1 << 4;	// 改变LED2的状态
	}
	return 0;
}

Makefile

uart.bin: start.o uart.o main.o
	arm-linux-ld -Ttext 0xD0020010 -o uart.elf $^
	arm-linux-objcopy -O binary uart.elf $@
	arm-linux-objdump -D uart.elf > uart.dis
	
%.o : %.c
	arm-linux-gcc -c $< -o $@ -fno-builtin
%.o : %.S
	arm-linux-gcc -c $< -o $@
	
clean:
	rm *.o *.elf *.bin *.dis

实验现象: 按数字 1 改变 LED1 的状态;按数字 2 改变 LED2 的状态。

由于我们在 uart.c 中使用了和 C 库同名的函数: putchar、 getchar、 puts,为了不和 C 库中的同名函
数发送冲突,需要给 gcc 加一个选项-fno-builtin,不使用内建函数。

问:为什么我们没有进行时钟配置相关的操作
答:因为 S5PV210 在启动时,运行 iROM 里的代码已经为我们初始化了时钟,其中 PCLK=66MHz

猜你喜欢

转载自blog.csdn.net/JerryGou/article/details/83025000