S3C2440中UART的简单配置 && 从零实现自己的printf函数

不管怎么样,都不能懒惰,努力努力再努力,加油!
以下是今昨两天的学习记录。

一、S3C2440中UART的简单配置

在开始配置串口之前我们得先知道下面几点

  1. UART有什么用,它是怎么工作的?
  2. S3C2440中有几个UART,我们需要配置哪个UART;
  3. 与我们要配置的UART连接的引脚有哪些,怎么样去设置它们;
  4. 我们要设置多大的波特率,怎么设置;
  5. UART发送\接收的数据格式需要设成什么样的,怎么设置;

1、UART可以用来打印调试信息,也可外接各种模块(如GPS、蓝牙等),它是通过设置波特率来约定与PC机或其他模块之间数据传输的速率。UART未工作时连接UART的引脚为高电平,当它开始传输数据或者接收数据时,与之相连的引脚随即被拉低 1bit 的时间(开始位),然后可以在对应的引脚上传输 0\1 数据了;
2、S3C2440中有总共有3个UART,他们分别是UART0,UART1和UART2,这里我配置的是UART0,其他两个UART的配置方法类似;
3、通过翻看原理图我们知道与UART0相连的引脚有GPH2和GPH3(如图1),由图2可知我们该将GPH2和GPH3设0b10,另外我们还需将GPH2和GPH3引脚设为上拉模式。

图 1
图 2
4、这里我将波特率设为115200bps,你也可以改变。由图3得知我们首先需要设置UART clock,然后再设置UBRDIVn寄存器。根据图4 UART clock我们选择PCLK(50MHz,在我第二篇博文里由说明

(点我),所以UCON0[11:10] = 00\01,将UART clock = 50M和baud rate = 115200带入图3公式得UBRDIV0 = 26,根据图5设置。

图 3
图 4
图 5
5、我们平常在连接串口时一般都会设置波特率、数据位、停止位、校验位和流量控制等,波特率我们前面已经设置过了,在这里我们设置8位数据位、1位停止位,无校验位,而流量控制我们暂时先不管。由图6我们可以对ULCON0进行相应的设置,我们不用红外模式即ULCON0[8],故ULCON0=0x00000003。
图 6

相应代码设置如下


#include "s3c2440_soc.h"

/* buad rate: 115200bps , 8n1 */
void uart0_init(void)	/* 初始化uart0 */
{
	/* 设置GPH2\3工作于uart模式, 内部拉高GPH2\3 */
	GPHCON &= ~((3<<4) | (3<<6));
	GPHCON |=  ((2<<4) | (2<<6));
	GPHUP &= ~((1<<2) | (1<<3)); 

	/* 设置波特率 
	 * UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1 
	 * 				UART clock = PCLK = 50MHz
	 * UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1
	 * 		   = 27 - 1
	 *		   = 26
	 * 
	 * 时钟选择 
	 * UCONn[11:10]: Clock Selection, 00, 10 = PCLK 01 = UEXTCLK 11 = FCLK/n
	 * UCONn[3:2]  : Transmit Mode
	 * UCONn[1:0]  : Receive Mode
	 */
	UCON0 &= ~((3<<10) | (3<<2) | (3<<0));
	UCON0 |=  ((0<<10) | (1<<2) | (1<<0));
	
	UBRDIV0 = 26;

	/* 设置数据格式 */
	ULCON0 = 0x00000003;		/* 8n1 : 8个数据位,无校验位,1个停止位 */
}

int putchar(int c)
{
	/* 写 UTXH0 */
	while (!(UTRSTAT0 & (1<<1)));	/* 检测UART TX/RX STATUS REGISTER 当发送为空时再发送数据 */
	UTXH0 = c;
}

int getchar(void)
{
	/* 读 URXH0 */
	while (!(UTRSTAT0 & (1<<0)));	/* 检测UART TX/RX STATUS REGISTER 当接收缓冲中接收到数据后再返回数据 */
	return URXH0;
}

int puts(const char *s)
{
	while (*s)
	{
		putchar(*s);
		s++;
	}
}

二、从零实现自己的printf函数

  1. 为什么要实现自己的printf函数,它能带来哪些好处?
  2. 我们平时用的printf是怎么声明的以及怎么对它进行传参?
  3. printf实现

1、因为我们是在裸机上进行编程的,我们确实可以以#include <stdio.h>的方式来调用printf函数(在另一篇博文中看到说包含stdio.h后的文件很大,但是我自己编译后的文件并不是太大,包括我写的其他的代码最终编译出来的文件差不多4k,所以我觉得也可以直接调用),但是这并不是我们学习的目的,因为那样做我们并学不到printf的内涵,而且这样编译的文件包含了我们许多我们不需要的数据(简直浪费空间),这都不是我们想要的,所以我们需要自己实现一个printf函数,通过串口用来打印我们的调试信息,以方便我们对程序进行调试。
2、我们通过man 3 printf在Linux中查看printf的声明为:
int printf(const char *format, ...);其中*fomat为固定参数(设为constx型是为了防止我们错误的修改),…为可变参数(变参)。在X86中函数调用时参数的传递是使用堆栈来实现的,用到堆栈就会涉及到指针的操作了(①取值,②移动指针),并且format保存的是固定参数的首地址。图7讲解了如何具体操作指针,这4个函数是包含在stdarg.h中的,下面让我们来看看他们具体是怎么声明和定义的

图 7
typedef char *  va_list;	/* 将va_list转换为char *类型,用来定义指针 */
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )	/* 按sieof(int)字节来进行字节对齐,sizeof(n)<=sizeof(int)的类型
都会被拓展为sieof(int)字节, arm9为32位,即sieof(int) = 4*/

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )	/* 移动指针,指向下一个值 */
//#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )	/* 这句比较难理解,下一条为化解版,便于理解 */
#define va_arg(ap,t)    ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )	/* 通过逗号表达式来取值和移动指针,逗号表达式返回的值为最后一个表达式
的值,因为第一个表达式移动了指针,故第二个表达式要先将指针移回再取值 */
#define va_end(ap)      ( ap = (va_list)0 )	/* 结束指针操作,使其指向0地址,避免野指针 */

(点击查看逗号表达式)

3、my_printf代码如下


#include "my_printf.h"

//==================================================================================================
typedef char *  va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
//#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_arg(ap,t)    ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )

//==================================================================================================
unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
		                 '8','9','a','b','c','d','e','f'};


static int outc(char c)		/* 调用 uart.c 中的 putchar 输出字符 */
{
	__out_putchar(c);
	return 0;
}

static int outs(char *s)	/* 输出字符串 */
{
	while (*s != '\0')
	{
		__out_putchar(*s++);
	}
	return 0;
}

static int out_num(long n, int base, char lead, int maxwidth)	/* 输出数字 */
{
	char buffer[MAX_NUMBER_BYTES], *s = buffer + sizeof(buffer);	/* 指向buffer顶部后的结束符 */
	unsigned long m = 0;
	int count = 0, i = 0;

	*--s = '\0';	/* 先往buffer顶部写入结束符 */

	if (n < 0)
	{
		m = -n;
	}
	else
	{
		m = n;
	}

	do
	{
		*--s = hex_tab[m%base];	/* 进制转换 */
		count++;					/* 对字符进行计数 */
	}while ((m /= base) != 0);

	if (maxwidth && count < maxwidth)		/* 如果指定了宽度,并且字符数小于宽度就加前导码 */
	{
		for (i = maxwidth - count; i; i--)
		{
			*--s = lead;
		}
	}

	if (n < 0)		
	{
		*--s = '-';
	}
	
	outs(s);
	
	return 0;
	
}

static int my_vprintf(const char *fmt, va_list p)
{
	char lead = ' ';	/* 前导码 */
	int maxwidth = 0;	/* 输出宽度 */

	for (; *fmt != '\0'; fmt++)	/* 未遇到结束符就一直输出有效字符 */
	{
		if (*fmt != '%')			/* 未遇到字符型格式符前导'%'就一直输出字符 */
		{
			outc(*fmt);
			continue;
		}

		lead = ' ';
		maxwidth = 0;

		fmt++;
		if (*fmt == '0')	/* 如果前导为0就进行更改 */
		{
			lead = '0';
			fmt++;
		}
		while (*fmt >= '0' && *fmt<= '9')	/* 设置输出字符宽度 */
		{
			maxwidth *= 10;
			maxwidth += *fmt - '0';
			fmt++;
		}

		switch (*fmt)	/* 对不同字符型格式符进行相应的处理 */
		{
			case 'd': out_num(va_arg(p, int), 			10,		lead, maxwidth); break;
			case 'o': out_num(va_arg(p, unsigned int), 	8,		lead, maxwidth); break;
			case 'u': out_num(va_arg(p, unsigned int), 	10,		lead, maxwidth); break;
			case 'x': out_num(va_arg(p, unsigned int), 	16,		lead, maxwidth); break;
			case 'c': outc(va_arg(p, int)); break;
			case 's': outs(va_arg(p, char *)); break;
			default: outc(*fmt); break;
		}	
	}
	return 0;
}

int printf(const char *fmt, ...)
{
	va_list p;
	va_start(p, fmt);
	my_vprintf(fmt, p);
	va_end(p);
	return 0;
}

int my_printf_test(void)
{
	printf("This is www.100ask.org   my_printf test\n\r") ;	
	printf("test char           =%c,%c\n\r", 'A','a') ;	
	printf("test decimal number =%d\n\r",    123456) ;
	printf("test decimal number =%d\n\r",    -123456) ;	
	printf("test hex     number =0x%x\n\r",  0x55aa55aa) ;	
	printf("test string         =%s\n\r",    "www.100ask.org") ;	
	printf("num=%08d\n\r",   12345);
	printf("num=%8d\n\r",    12345);
	printf("num=0x%08x\n\r", 0x12345);
	printf("num=0x%8x\n\r",  0x12345);
	printf("num=0x%02x\n\r", 0x1);
	printf("num=0x%2x\n\r",  0x1);

	printf("num=%05d\n\r", 0x1);
	printf("num=%5d\n\r",  0x1);
	
	return 0;
	
}
 

my_printf.h如下


#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H

#include "uart.h"
#define __out_putchar putchar

#define MAX_NUMBER_BYTES 64

int printf(const char *fmt, ...);
int my_printf_test(void);

#endif		/* _MY_PRINTF_H */

其中putchar在上文串口设置中已经实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值