007. UART——韦东山第一期

1 UART协议

  • USART是一种全双工、异步通讯协议,常用来串口调试。

  • 起始位:一个机器周期的低电平。

  • 数据位:8个机器周期的高低电平序列。

  • 校验位:奇偶校验,校验位加有效数据位为奇数个或偶数个。

  • 停止位:1个或者两个连续机器周期的低电平。

  • 波特率:发送一位数据的速率。

115200   8nl  # 无校验,加上停止位和起始位一共10位数据
发送1bit的时间:1/115200
发送1byte的时间:1/11520
那么一秒发送字节数:11520

2 USART框图

在这里插入图片描述

3 USART接线

在这里插入图片描述

4 寄存器介绍

4.1 uart线控制寄存器(ULCONn)
  • 设置无红外、无校验、一个停止位、8位字长。
  • ULCON0 = 0x03

在这里插入图片描述

4.2 uart控制寄存器(UCON0)
  • 选择时钟源为PCLK,配置RX与TX为中断或轮询。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.3 FIFO控制寄存器(UFCONn)
  • 先别管,FIFO用在大量数据传输上。

在这里插入图片描述

4.4 uart调制调解控制器(UMCONn)
  • 与流控相关,关闭就行。

在这里插入图片描述

4.5 TXD和TRD状态寄存器(UTRSTATn)
  • 移位寄存器->发送缓冲区->接收缓存缓冲区。
  • 只要发射机为NULL,表名移位寄存器空了,意味着可以写入数据,否则上次的还没发出去。
  • 发送缓冲区为NULL,结束了本次发送。
  • 接收缓冲区为NULL,结束了整个接收。
  • 用来检测发送和接收是否结束。

在这里插入图片描述

4.6 错误状态寄存器(UERSTATn)
  • 用来检测错误,暂时不管。

在这里插入图片描述

4.7 FIFO状态寄存器(UFSTATn)
  • 暂时不管。

在这里插入图片描述

4.8 调制调节器状态寄存器(UMATATn)
  • 暂时不管。
    在这里插入图片描述
4.9 发送缓冲区(UTXHn)
  • 8位对应了发送的数据。
  • 需要将头文件里的地址改为char *,使用小端模式。
"s3c2440_soc.h"
#define     __REG_BYTE(x)				(*(volatile unsigned char *)(x)) 
#define     UTXH0                    __REG_BYTE(0x50000020)  //UART 0 transmission hold 
#define     URXH0                    __REG_BYTE(0x50000024)  //UART 0 receive buffer    

在这里插入图片描述

4.10 接收缓冲区寄存器(URXHn)
  • 8位对应着接收缓冲区的值。

在这里插入图片描述
在这里插入图片描述

4.11 波特率除位寄存器(UBRDIVn)
  • UBRDIVn:波特率分频寄存器的值,需要计算后人为分配,范围16位。

  • UART clock:可以使PCLK、FCLK、UEXTCLK(小于PCLK),需要选择。

  • UCONn:用来选择哪一个作为时钟源,[11,10] = 00时选择PCLK,此处为UART0,故配置UCON0。

在这里插入图片描述

5 设置流程

(1)配置引脚为上拉模式:

​ 需要先配置GPH3和GPH2的GPHCON寄存器,设置模式。

​ 还需要配置GPHUP的2、3位为上拉模式。

在这里插入图片描述
在这里插入图片描述

(2)配置数据格式:ULCON0=0x03,配置数据位8n1。

(3)配置时钟源:UCON0=0x05,配置时钟源为PCLK,中断或轮询模式。

(4)配置波特率:UBRDIV0 =26,经过公式计算可知。

(5)判断发送接受状态: 读取UTRSTAT0寄存器判断发送或接收结束。

(6)发送或接收:写发送缓冲寄存器UTXH0或读取接收缓冲寄存器URXH0。

6 实现UART打字显示

6.1 uart.h
#ifndef UART_H__
#define UART_H__  /*防止重复包含*/

void uart_init(void);
int putchar(int c);
int getchar(void);
int puts(const char *s);

#endif
6.2 uart.c
#include "s3c2440_soc.h"

void uart_init(void){
	/*******配置引脚的工作模式以及上拉******/
	GPHCON &= ~((3 << 4) | (3 << 6));  /*清零*/
	GPHCON |= (2 << 4) | (2 << 6);  /*配置模式为2*/
	GPHUP &= ~(1 << 2 | 1 << 3);/*将引脚电平上拉*/
	/***************************************/
	
	/**************设置数据格式***************/
	ULCON0 = 3; /*无校验、无红外、8位字长、一个停止位*/
	/*****************************************/
	
	/**************设置时钟源***************/
	UCON0 = 5; /*PCLK模式、中断轮询模式*/
	/***************************************/
	
	/**************设置波特率***************/
	UBRDIV0 = 26; /*波特率分频器*/
	/***************************************/
	
}

int putchar(int c){
	
	/*检测发送标志位 UTRSTAT的第二位置为1,则发送缓冲区空了可以放数据*/
	while(!(UTRSTAT0 & (1 << 2))); 
	UTXH0 = (unsigned char)c; /*为NULL才赋值给缓冲区*/
	
}

int getchar(void){
	/*检测发送标志位 UTRSTAT的第0位置为1,则接收缓冲区已经收到数据*/
	while(!(UTRSTAT0 & (1 << 0)));
	return (int)URXH0;
}

int puts(const char *s)
{
	/*使用指针来控制 *(s + 1) 则为下一个地址的值,结束条件不为NULL*/
	while(*s != '\0'){
		putchar(*s);
		s++;
	}
}
6.3 uart_test.c
#include "s3c2440_soc.h"
#include "uart.h"

int main(void){
	unsigned char c;
	uart_init();
	puts("write test\r\n");
	while(1){
		c = getchar();
		if(c == '\r'){
			putchar('\n');
		}
		if(c == '\n'){
			putchar('\r');
		}
		putchar(c);
	}
	return 0;
}
6.4 start.S
.text
.global _start
_start:
	/*========= 设置看门狗==========*/
	mov r0,#0
	ldr r1,=0x53000000 
	str r0,[r1]
	
	
	/*========= 设置时钟 ==========*/
	
	/*配置locktime*/
	ldr r0,=0xFFFFFFFF
	ldr r1,=0x4C000000 
	str r0,[r1]
	
	/*配置CLKDIVN*/
	mov r0,#5
	ldr r1,=0x4C000014
	str r0,[r1]
	
	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0
	
	/*配置MPLLCON*/
	ldr r0,=(92 << 12)|(1 << 4) |(1 << 0)
	ldr r1,=0x4C000004 
	str r0,[r1]

	/*=========检查nand还是nor启动==========*/
	/*假设sp从nand启动*/
	ldr sp,=0x40000000+4096
	/*读取地址0的数据到r1*/
	mov r0,#0 
	ldr r1,[r0]
	/*写0到0地址*/
	str r0,[r0]
	/*将修改后的值放到r2*/
	ldr r2,[r0]
	/*判断修改成功与否*/
	cmp r0,r2
	/*如果相等则为nor启动,将栈修改位4096*/
	moveq sp,#4096
	/*如果为nor启动需要恢复原来的值*/
	str r1,[r0]
	
	
	/*=========跳转到main,并保留pc指针============*/
	bl main

/*=======死循环=========*/
_halt:
	b _halt
6.5 Makefile
all:
	# 生成目标文件
	arm-linux-gcc -c -o uart.o uart.c -nostdlib   # c代码都需要不调用标准库
	arm-linux-gcc -c -o uart_test.o uart_test.c -nostdlib
	arm-linux-gcc -c -o start.o start.S

	arm-linux-ld -Ttext 0 start.o uart.o uart_test.o -o uart.elf
	arm-linux-objcopy -O binary -S uart.elf uart.bin
	arm-linux-objdump -D uart.elf > uart.dis
clean:
	rm *.bin *.o *.elf *.dis

7 printf底层

7.1 printf的参数存放
#include <stdio.h>
 int printf(const char *format, ...);
/*
	format 为可变参数,存放第一个参数的地址,其地址加sizeof(char *)为下一个内容的地址。
	再对地址的内容解引用,按照相应的格式输出即可。
*/

在这里插入图片描述

在这里插入图片描述

7.2 结构体地址对齐
#include <stdio.h>

/*
	可变参数是用栈存储的
	format是第一个元素的地址
	format + sizeof(char *)为下一个可变参数地址
	浮点数默认为double,传参后默认为double类型
*/

struct Stu{
	char *name;
	int age;
	char score;
	int height;
}__attribute((packed));

void push_test(const char *format,...){
    /*
    	① 取值
    	② 移动到下一个可变参数
    */
	char *p = (char *)(&format); 
	struct Stu s;
    /***取值***/
	printf("format = %s\n",format); /*打印format*/
    /***移动到下一个地址***/
	p += sizeof(char *);
	
	printf("... = %d\n",*((int*)p)); /*打印整数*/
	p += sizeof(char *);
	s = *(struct Stu*)p;
	printf("stu.name = %s\nstu.age = %d\nstu.score=%c\nstu.height=%d\n",s.name,s.age,s.score,s.height);
}

int main(int argc,char **argv){
	struct Stu s = {"simon",18,'A',180};
	/*
		结构体默认4字节对齐,相当于结构体定义后面加了 __attribute((aligned(4)))
		加__attribute((packed))取消对齐
	*/
	
	printf("sizeof(Stu) = %d\n",sizeof(s));
	printf("1_%p\n",&(s.name));
	printf("2_%p\n",&(s.age));
	printf("3_%p\n",&(s.score));
	printf("4_%p\n",&(s.height));
	push_test("abc",123,s);
	return 0;
}
7.3 操作可变参数的宏定义
![7_22](H:\嵌入式笔记\100Ask\images\7_22.bmp)/*可变参数宏的含义*/
#include <stdarg.h>
va_list    p;  		// 相当于指针p          
va_start(p,format);   // 相当于移动p + sizeof(char *)  ,这一步后面的就是可变参数了
va_arg( p, 变量类型);    // 相当于先取值给val再移动地址   
va_end( p );        // 相当于让p指向NULL,避免野指针
/*可变参数宏的底层 vc6.0*/
 /*定义一个char *指针用来保存参数地址*/
typedef char *  va_list;
/*宏实现地址对齐的移动,如果4字节对齐,那么便宜量为4*/
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
/*相当于取format的地址并加上一个偏移量*/
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
/*
完成取值和移动指针
先将地址移动4字节,再将移动后的地址减4字节的值取出来
*/
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
/*将指针设置为NULL,避免野指针*/
#define va_end(ap)      ( ap = (va_list)0 )

在这里插入图片描述

8 自定义printf实现arm移植

8.1 bsp_uart.h
#ifndef BSP_UART_H__
#define BSP_UART_H__

void uart_init(void);
int putchar(int c);
int getchar(void);

#endif


8.2 bsp_uart.c
#include "s3c2440_soc.h"
#include "bsp_uart.h"

void uart_init(void){
	/*******配置引脚的工作模式以及上拉******/
	GPHCON &= ~((3 << 4) | (3 << 6));  /*清零*/
	GPHCON |= (2 << 4) | (2 << 6);  /*配置模式为2*/
	GPHUP &= ~(1 << 2 | 1 << 3);/*将引脚电平上拉*/
	/***************************************/
	
	/**************设置数据格式***************/
	ULCON0 = 3; /*无校验、无红外、8位字长、一个停止位*/
	/*****************************************/
	
	/**************设置时钟源***************/
	UCON0 = 5; /*PCLK模式、中断轮询模式*/
	/***************************************/
	
	/**************设置波特率***************/
	UBRDIV0 = 26; /*波特率分频器*/
	/***************************************/
	
}

int putchar(int c){
	
	/*检测发送标志位 UTRSTAT的第二位置为1,则发送缓冲区空了可以放数据*/
	while(!(UTRSTAT0 & (1 << 2))); 
	UTXH0 = (unsigned char)c; /*为NULL才赋值给缓冲区*/
	
}

int getchar(void){
	/*检测发送标志位 UTRSTAT的第0位置为1,则接收缓冲区已经收到数据*/
	while(!(UTRSTAT0 & (1 << 0)));
	return (int)URXH0;
}

8.3 bsp_io.h
#ifndef BSP_IO_H__
#define BSP_IO_H__
/*******************头文件*****************/
#define GCC_M__  0 /*调gcc的stdio库*/
#define UART_M__ 1 /*调arm开发板的uart库*/

#if GCC_M__
#include <stdio.h>
#elif UART_M__
#include "bsp_uart.h"
#endif

/********************定义宏命令************/

/*使用m_变量去调用原始的putchar可能是stdio也可能是uart的*/
#define m_putchar putchar

/*相当于一个char指针*/
typedef char *  va_list;   

/*获得4字节对齐的指针偏移量*/
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

/*获取地址加4*/
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )

/*将取值和地址加4整合*/
#define va_arg(ap,t)    ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )

/*将指针置为NULL,避免野指针*/
#define va_end(ap)      ( ap = (va_list)0 )

/*****************函数的声明***************/


/*printf函数的改写*/
int printf(const char *fmt, ...);


#endif

8.4 bsp_io.c
#include "bsp_io.h"  /*引入头文件*/

/*打印字符串*/
static int m_puts(const char *s){
	while(*s != '\0'){
		m_putchar(*s++);
	}
	return 0;
}

/**********************打印数字*********************/

/*定义一个数组,里面存16进制的0~f*/
unsigned char hex_table[] = {'0','1','2','3','4','5','6','7',\
		                 '8','9','a','b','c','d','e','f'};

/*
	参数:n——数字 base——进制 lead——前面的数是0还是空格 maxwidth 总长度
	
	如 (10,2,0,4);   0000 1010  前部补4个lead
	思路:
		1. 先找最后字符串的末尾指针地址
		2. 判断n为正数还是负数,取绝对值
		3. 将n的base的余数作为数组的下表查找对应字符放到字符串中
		4. 将lead部分放到前面的地址
		5. 先打印正负号,再puts整个字符串

*/

static int out_num(long n, int base,char lead,int maxwidth){
	unsigned long m = 0;/*无符号数记录n的绝对值*/
	char buf[64]; /*声明一个字符数组*/
	/*s指向即最后一个元素的尾地址*/
	char *s = buf + sizeof(buf); 
	int count = 0,i = 0;
	/*先将buf的末尾改为NULL字符*/
	/*此时s需要减一个地址才会等到最后一个元素的起始地址*/
	*--s = '\0';
	/*判断正还是负,打印符号*/
	if(n < 0){
		m = -n;
	}else{
		m = n;
	}
	/*将后面的元素全部保存为字符串且存在字符数组后半段*/
	do{
		*--s = hex_table[m%base];
		count ++; /*记录数字出现的次数,便于后面求0的个数*/
		m /= base;
	}while(m);
	
	/*将0 或 空格保存到字符数组*/
	i = maxwidth - count;
	while(i > 0){
		*--s = lead;
		i --;
	}
	
	
	/*把符号保存到字符数组中*/
	if(n > 0){
		*--s = '+';
	}else{
		*--s = '-';
	}
	
	/*打印s*/
	m_puts(s);
	return 0;
}



/*****************静态函数去处理printf中的打印内容*************/

static int help_printf(const char *format, va_list p){
	/*
	* 1. 没遇到%号的连续打印。
	* 2. 遇到%号的需要判断。
	*		%x 十六进制
	*		%d 十进制打印
	*       %u 无符号十进制打印
	*  		%c 字符打印
	*		%s 输出字符串
	*		0  以0填充
	*		默认 以空格填充
	*/
	char leap = ' '; /*默认空格填充*/
	int maxwidth = 0;
	/******遍历format字符串*****/
	while(*format != '\0'){
		/*当字符串内容为'\0'打印结束*/
		
		if(*format == '%'){
			/*当遇到了%则进入循环打印出该格式符号对应的字符串*/
			format++;
			if(*format == '0'){
				/*代表有数字0填充*/
				leap = '0';
				format++;
			}
			/*接下来就应该判断规定了多长的打印长度*/
			while(*format >= '0' && *format <= '9'){
				/*当字符为0~9的数字时,需要将其拼接*/
				maxwidth *= 10; /*先将自身乘以10,加上format的值*/
				maxwidth += (*format-'0');
				format++;
			}
			/*处理最后的修饰符号*/
			switch(*format){
				case 'd': /*打有符号十进制数*/
					out_num(va_arg(p,int), 10, leap, maxwidth);
					break;
				case 'u': /*打印无符号十进制数*/
					out_num(va_arg(p,unsigned int),10,leap,maxwidth);
					break;
				case 'x': /*打印十六进制数*/
					out_num(va_arg(p,int),16,leap,maxwidth);
					break;
				case 'o': /*打印八进制数*/
					out_num(va_arg(p,int),8,leap,maxwidth);
					break;
				case 'c': /*打印字符*/
					m_putchar(va_arg(p,char));
					break;
				case 's': /*打印字符串*/
					m_puts(va_arg(p, char*));
					break;
			}
		}else{
			/*不为%正常输出*/
			m_putchar(*format);
		}
		
		/*每次都会地址加1*/
		format ++;
	}
	return 0;
}


/***************printf函数的改写*********************/
int printf(const char *format, ...){
	/*声明一个指针*/
	va_list p;
	/*将指针指向第一个变参位置*/
	va_start(p,format);
	/*调用函数处理中间的打印内容*/
	help_printf(format,p);
	/*将指针置null*/
	va_end(p);
	return 0;
}



8.5 Makefile
all:
	arm-linux-gcc -c -o bsp_uart.o bsp_uart.c  
	arm-linux-gcc -c -o bsp_io.o bsp_io.c 
	arm-linux-gcc -c -o main.o main.c 
	arm-linux-gcc -c -o lib1funcs.o lib1funcs.S
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 -Tdata 0xb10 start.o lib1funcs.o bsp_uart.o bsp_io.o main.o -o demo.elf
	arm-linux-objcopy -O binary -S demo.elf demo.bin
	arm-linux-objdump -D demo.elf > demo.dis
	
clean:
	rm *.bin *.o *.elf *.dis
8.6 main.c
#include "s3c2440_soc.h"
#include "bsp_io.h"
#include "bsp_uart.h"

int main(void){
	unsigned char ch;
	uart_init();
	printf("%c%c%s\n\r",'a','b',"hello");
	printf("%s: %d\n\r","total",123);
	printf("%0x\n\r",0xfff);
	printf("=====test======\r\n");
	while(1){
		ch = getchar();
		if(ch == 027){ /*esc的ASCII码*/
			break;
		}
		if(ch == '\r'){
			putchar('\n');
		}else if(ch == '\n'){
			putchar('\r');
		}
		putchar(ch);
	}
	printf("test is end\r\n");
	return 0;
}


8.7 缺少除法库

8.6.1 指定静态库

# 在arm-linuxgcc的安装目录下有一个libgcc.a的静态库。
# 在链接时需要链接该静态库。
arm-linux-ld -Ttext 0  -Tdata 0xe80 start.o bsp_uart.o bsp_io.o main.o -o demo.elf  -lgcc -L/usr/local/arm/arm-2009q3/lib/gcc/arm-none-linux-gnueabi/4.4.1
# 在main函数里加个raise函数
int raise(int a)
{
	return 0;
}

#include "bsp_io.h"


int main(void){
	
	printf("%c%c%s\n",'a','b',"hello");
	printf("%s: %d\n","total",123);
	printf("%0x\n",0xfff);
	
	return 0;
}
# 需要指定代码的长度,因为2440开发板只有4KRAM不够

8.6.2 直接替换arm-linux-gcc

# xshell远程连接ubuntu
sudo apt-get install lrzsz
# 使用zmordm传输文件
rz -E # 选择对应的文件
# 创建目录
sudo mkdir -p /usr/local/arm
# 解压安装包
sudo tar -jxvf arm-linux-gcc-3.4.5-glibc-2.3.6.tar.bz2 -C /usr/local/arm
# 安装路径设置到 ~/.bashrc
sudo vim ~/.bashrc #作用于该用户,每次开机都调用
# 输入环境变量
export PATH=$PATH:/usr/local/arm/4.3.5/bin
# 激活环境变量
source ~/.bashrc
# 查看安装成功与否
arm-linux-gcc -v
# 将lib1funcs.S添加到工程

9 注意事项

9.1 末尾空格
  • linux的c和S文件都需要在行尾加空格,类似于KEIL MDK。
9.2 内存不够
# 2440的SRAM仅仅4K,存不下,需要查看dis反汇编文件

# 找到_data_start,数据段起始位置,一般将其拼接到上面的代码段后面,如 末尾为b08替换为b10,大一点点就行
/section # 按下n向下找找
# 在链接时设置该数据段起始位置
arm-linux-ld -Ttext 0 -Tdata 0xb10 start.o lib1funcs.o bsp_uart.o bsp_io.o main.o -o demo.elf

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值