不管怎么样,都不能懒惰,努力努力再努力,加油!
以下是今昨两天的学习记录。
一、S3C2440中UART的简单配置
在开始配置串口之前我们得先知道下面几点
- UART有什么用,它是怎么工作的?
- S3C2440中有几个UART,我们需要配置哪个UART;
- 与我们要配置的UART连接的引脚有哪些,怎么样去设置它们;
- 我们要设置多大的波特率,怎么设置;
- 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引脚设为上拉模式。
|
|
(点我),所以UCON0[11:10] = 00\01,将UART clock = 50M和baud rate = 115200带入图3公式得UBRDIV0 = 26,根据图5设置。
|
|
|
|
相应代码设置如下
#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函数
- 为什么要实现自己的printf函数,它能带来哪些好处?
- 我们平时用的printf是怎么声明的以及怎么对它进行传参?
- 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中的,下面让我们来看看他们具体是怎么声明和定义的
|
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在上文串口设置中已经实现