韦一之串口、va_arg()可变参数处理及裸机实现printf(重难)(011课)

这篇博客介绍了串口基础知识,包括波特率、数据位和校验位的设置,以及S3C2440芯片的UART工作原理。接着,博主探讨了编程实现串口通信,包括配置寄存器和发送数据的过程。文章还讨论了`printf`函数的可变参数处理,解释了如何手动和自动处理变参,以及在裸机环境中实现`printf`的思路和方法。
摘要由CSDN通过智能技术生成

参考了https://www.cnblogs.com/huangdengtao/p/12103149.html

一、串口基础

写法:115200,8n1表示波特率115200,8位数据位,没有(No)校验位,一个停止位(还有一个必备的起始位)
所以共十位,每一位需要时间1/115200,传输一个字节的时间也就是1/11520
也就是对应于8n1的方式,一秒可以传输11520字节数据!

怎么发送一字节数据,比如‘A‘?
A 的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给PC机呢?

a. 平时数据线处于“空闲”状态(1状态)
对于TTL电平来说就是原来是高电平,ARM拉低电平,保持1bit时间;

b. 当要发送数据时,UART改变TxD数据线的状态(变为0状态),并维持1位的时间,这样接收方检测到开始位后,在等待1.5位的时间就开始一位一位的检测数据线的状态得到所传输的数据 。(PC在低电平开始处计时;)
c. ARM根据数据依次驱动TxD的电平,同时PC依次读取RxD引脚电平,获得数据;
首先发送最低位
有两种校验方法:奇校验,偶校验——数据位连同校验位,“1”的数目等于奇数或偶数。

前面图中提及到了逻辑电平,也就是说代表信号1的引脚电平是人为规定的。
如图是TTL/CMOS逻辑电平下,传输‘A’时的波形:
在这里插入图片描述
在这里插入图片描述
上面的两种方式,对ARM芯片的编程操作都是一样的。

S3C2440中UART的工作过程:
在这里插入图片描述
S3C2440UART的FIFO深度为64。发送数据时,CPU先将数据写入发送FIFO中,然后UART会自动将FIFO中的数据复制到“发送移位器”中,发送移位器将数据一位一位的发送到TxDn数据线上(根据设定的格式,插入开始位,校验位和停止位)。接收数据时,“接收移位器”将RxDn数据线上的数据一位一位接收进来,然后复制到接收FIFO中,CPU即可从中读取数据。

二、编程

配置寄存器某两个位记得要先清零再赋值(因为赋值都是用或的)
如: GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));

GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */
平时空闲得让串口的引脚是高电平,所以使这里为0,内部上拉使能,成高电平。

注意:在s3c2440.h中存在着寄存器的定义(在代码中就可以直接用如GPACON了)
#define __REG(x) (*(volatile unsigned int )(x))
#define GPACON __REG(0x56000000) //Port A control
其中,大部分寄存器(几乎所有)都是用的
*(volatile unsigned int )* ,因为是32位
但是!!!在手册里搜索UTXH0 可以发现这个寄存器是write by byte,只按一个字节访问
0x50000020(L)指的是使用小字节序时使用这个地址(大字节序对应使用地址不同)。
所以,要在s3c2440.h中按照如下定义:(用到时检查一下,可能s3c2440.h中有错误用的是int)
#define __REG_BYTE(x) *(volatile unsigned char *)(x))
#define UTXH0 __REG_BYTE(0x50000020) //UART 0 transmission hold

在这里插入图片描述
(用法!!!!!!!!!!!!)

int putchar(int c)
{
   
	while (!(UTRSTAT0 & (1<<2)));
	UTXH0 = (unsigned char)c;//!!!!!!!!!!!!!!!!!!
}

在这里插入图片描述
代码编写:
在这里插入图片描述
uart.c

#include "s3c2440_soc.h"

/*	115200, 8n1
 *	8: 数据位
 *	n: 没有校验位
 *  1: 停止位
 */
void uart0_init()
{
   
	/*	设置引脚	*/
	GPHCON &= ~((3<<4) | (3<<6));
	GPHCON |= ((2<<4) | (2<<6));

	/*	设置上拉	
	 *	因为uart传输协议要求开始空闲为高电平
	 */
	GPHUP &= ((1<<2) | (1<<3));
	
	/*	设置波特率	*/
	/*  UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
	 *  UART clock = 50M
	 *  UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26
	 */
	UCON0 = 0x00000005;		/*	PCLK,中断/查询模式	*/
	UBRDIV0 = 26;
		
	/*	设置数据格式	*/
	ULCON0 = 0x00000003;
}

int putchar(int c)
{
   
	while(!(UTRSTAT0 & (1<<2)));
	UTXH0 = (unsigned char)c;
}

int getchar(void)
{
   
	while(!(UTRSTAT0 & (1<<0)));
	return URXH0;
}

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

代码main.c:

#include "s3c2440_soc.h"
#include "uart.h"

main(int argc, char **argv)
{
   
	unsigned char c;
	uart0_init();
	puts("Hello world\n\r");

	while(1)
	{
   
		c = getchar();
		if(c == '\r')
		{
   
			putchar('\n');
		}
		if(c == '\n')
		{
   
			putchar('\r');
		}

		putchar(c);
	}

	return 0;
}

现象:开发板上电电脑串口(mobaxterm软件)会接收显示Hello, world!

补:有漏洞的程序如下
puts(“Hello, world!\n”);
while(1)
{
c = getchar();
putchar©;(回显功能)
}
puts(“Hello, world!\n\r”)
如果这里额没有\r 则再输入什么字母都会在下一行显示出来,不过会发现不是在行首,有缩进。
同时,在第二行键盘输入什么字母本应该会显示出什么字母,但是如果敲击回车不会进入下一行,而是跑到了本行行首
这是因为有些串口工具敲击回车时只会认为是’\n’或者’\r’,所以有了上面main代码。

\r 代表着回到行首,\n就是"到下一行"
即:\r是回车,\n是换行,前者使光标到行首,后者使光标下移一格。
通常用的Enter是两个加起来的,即\r\n。

Unix系统里,每行结尾只有“<换行>”,即“\n”(0x0A);
Windows系统里面,每行结尾是“<换行><回车>”,即“\n\r”;(0x0D和0x0A两个字符)

Mac系统里,每行结尾是“<回车>”。

补充:mobaxterm软件有个缺陷,如果先打开软件,再接串口的话是无法打开那个串口的。
不会动态监测电脑串口。要关了软件,连好串口再打开软件。

printf,可变参数的处理

正版的库函数printf,scanf函数特别庞大!!我们片内内存只有4K,运行不了。
自己写c语言应用程序的时候,经常会使用printf来打印。
printf在是一个标准库函数,功能是:打印(变量、字符串)等等。
问题:能不能依据printf的原理,写一个简易的用于裸机程序调试的my_printf函数呢?
好处:1)my_printf函数在单片机、嵌入式芯片裸机调试过程中非常方便。
2)my_printf函数可以帮你打印寄存器的值、变量的值、打印字符串等。

标准printf的声明
int printf(const char *format, …);
format:固定参数
… :可变参数(变参)
例如
printf(“This is www.100ask.org my_printf test\n”) ;
printf(“test char =%c,%c\n”, ‘A’,‘a’) ;
printf(“test decimal number =%d\n”, 123456) ;

printf中的格式字符:
在这里插入图片描述

手动确定可变参数,由printf打印出来

根据我们事先知道的实际实参类型,写的对可变参数处理的指针的移动,类型转换和取值。
这个实验是在ubuntu进行的,用的工具是gcc。目的:将所有传入的参数全部打印出来
测试平台: ubuntu16.04(64位机器) gcc -m32 -o push_test push_test.c
ubuntu9.10 (32位机器) gcc -o push_test push_test.c

#include <stdio.h>//由此可见就不能是裸机程序,裸机不能用这个。
struct  person{
   
	char *name;
	int  age;
	char score;
	int  id;
};
int main(int argc,char **argv)
{
   
	struct  person per={
   "www.100ask.org",10,'A',123};
	printf("sizeof(struct  person)=%d\n",sizeof(struct  person));		 
    
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值