该寄存器通用配置为:
UCON2 = 0x5; //Interrupt request or polling mode
一般裸机情况下,采用轮询模式。
UTRSTATn
UTRSTAT n寄存器用来表明数据是否已经发送完毕、是否已经接收到数据,格式如下图所示,上面说的“缓冲区”,其实就是下图中的 FIFO ,不使用 FIFO 功能时可以认为其深度为 1。
当我们读取数据时,就轮询检查bit[0]置1之后,然后再从URXHn寄存器读取数据;当我们读取数据时,就轮询检查bit[1]置1之后,然后再向UTXHn寄存器写入数据来发送数据;
UTXHn寄存器(UART TRANSMIT BUFFER REGISTER) CPU 将数据写入这个寄存器, UART即会将它保存到缓冲区中,并自动发送出去。
URXHn寄存器(UART RECEIVE BUFFER REGISTER) 当 UART 接收到数据时,读取这个寄存器,即可获得数据。
UFRACVALn 计算波特率
根据给定的波特率、所选择时钟源频率,可以通过以下公式计算 UBRDIVn 寄存器 (n 为 0~4,对应 5个 UART 通道 )的值。
UBRDIVn = (int)( UART clock / ( buad rate x 16) ) – 1
上式计算出来的 UBRDIVn 寄存器值不一定是整数, UBRDIVn 寄存器取其整数部分,小部分由 UFRACVALn 寄存器设置, UFRACVALn 寄存器的引入,使产生波特率更加精确。「【举例】」当UART clock为100MHz时,要求波特率为115200 bps,则:
100000000/(115200 x 16) – 1 = 54.25 – 1 = 53.25
UBRDIVn = 整数部分 = 53
UFRACVALn/16 = 小数部分 = 0.25
UFRACVALn = 4
5)电路图
外设电路图:
SP3232EEA 用来将TTL电平转换成RS232电平。我们使用的是COM2。
外设与核心板连接电路图
可见UART的收发引脚连接到了GPA上,打开exynos4412芯片手册:
我们只需要将GPA1 的低8位设置为0x22。
6.实例代码
裸机代码,主要实现uart_init()、putc()、getc()这三个函数。
uart_init()
该函数主要配置UART的,波特率115200,数据位:8,奇偶校验位:0,终止位:1,不设置流控。
如下图:是运行在windows下常用的串口工具配置信息,配置信息必须完全一致。
putc()
该函数是向串口发送一个数据data,他的实现逻辑就是轮询检查寄存器UART2.UTRSTAT2 ,判断其bite【1】是否置1,如果置1,则向UART2.UTXH2存入要发送的数据即可。
getc()
该函数是从串口接收一个数据data,他的实现逻辑就是轮询检查寄存器UART2.UTRSTAT2 ,判断其bite【0】是否置1,如果置1,说明数据准备好,则可以从寄存器UART2.URXH2取出数据。
代码
* UART2
typedef struct {
unsigned int ULCON2;
unsigned int UCON2;
unsigned int UFCON2;
unsigned int UMCON2;
unsigned int UTRSTAT2;
unsigned int UERSTAT2;
unsigned int UFSTAT2;
unsigned int UMSTAT2;
unsigned int UTXH2;
unsigned int URXH2;
unsigned int UBRDIV2;
unsigned int UFRACVAL2;
unsigned int UINTP2;
unsigned int UINTSP2;
unsigned int UINTM2;
}uart2;
#define UART2 ( * (volatile uart2 *)0x13820000 )
GPA1
typedef struct {
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
unsigned int CONPDN;
unsigned int PUDPDN;
}gpa1;
#define GPA1 (* (volatile gpa1 *)0x11400020)
void uart_init()
{ UART2 initialize
GPA1.CON = (GPA1.CON & ~0xFF ) | (0x22); //GPA1_0:RX;GPA1_1:TX
UART2.ULCON2 = 0x3; //Normal mode, No parity,One stop bit,8 data bits
UART2.UCON2 = 0x5; //Interrupt request or polling mode
//Baud-rate : src_clock:100Mhz
UART2.UBRDIV2 = 0x35;
UART2.UFRACVAL2 = 0x4;
}
void putc(const char data)
{ while(!(UART2.UTRSTAT2 & 0X2));
UART2.UTXH2 = data;
if (data == '')
putc('');
}
char getc(void)
{ char data;
while(!(UART2.UTRSTAT2 & 0x1));
data = UART2.URXH2;
if ((data == '')||(data == ''))
{
putc('');
putc('');
}else
putc(data);
return data;
}
puts/getsvoid puts(const char *pstr)
{ while(*pstr != '')
putc(*pstr++);
}
void gets(char *p)
{ char data;
while((data = getc())!= '')
{ if(data == '')
{p--;
}
*p++ = data;
}
if(data == '')
*p++ = '';
*p = '';
}
7.如何裸机程序可以支持printf函数
首先看下文件的目录结构:
代码架构
老规矩,关注,后台回复【armprintf】,就可以得到代码。
这里我们只贴出部分文件的代码。
「cpu/start.s」改文件主要是实现异常向量表,实现各个模式的栈初始化
.text
.global _start
_start:
b reset
ldr pc,_undefined_instruction
ldr pc,_software_interrupt
ldr pc,_prefetch_abort
ldr pc,_data_abort
ldr pc,_not_used
ldr pc,=irq_handler
ldr pc,_fiq
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word irq_handler
_fiq: .word _fiq
reset:
ldr r0,=0x40008000
mcr p15,0,r0,c12,c0,0 @ 协处理器指令设置异常向量表地址
init_stack:
ldr r0,stacktop get stack top pointer
*******svc mode stack*******
mov sp,r0
sub r0,#128*4 512 byte for irq mode of stack
***irq mode stack*
msr cpsr,#0xd2
mov sp,r0
sub r0,#128*4 512 byte for irq mode of stack
**fiq mode stack**
msr cpsr,#0xd1
mov sp,r0
sub r0,#0
**abort mode stack**
msr cpsr,#0xd7
mov sp,r0
sub r0,#0
**undefine mode stack**
msr cpsr,#0xdb
mov sp,r0
sub r0,#0
** sys mode and usr mode stack **
msr cpsr,#0x10
mov sp,r0 1024 byte for user mode of stack
b main @跳转到c语言的main函数
.align 4
*** swi_interrupt handler ***
*** irq_handler ***
irq_handler:
sub lr,lr,#4
stmfd sp!,{r0-r12,lr}
.weak do_irq @该函数可以没有定义
bl do_irq @跳转到中断入口
ldmfd sp!,{r0-r12,pc}^
stacktop: .word stack+4*512 @定义栈顶
.data
stack: .space 4*512 @分配一块栈空间
「lib/printf.c」
该文件主要实现打印函数printf一些格式控制,一些字符串转换算数运算需要借助头文件ctype.h、stdarg.h中一些宏。其中vsprintf 具体的实现我们就不再详解,有兴趣读者自行研究。
……
void printf (const char *fmt, ...)
{
va_list args;
unsigned int i;
char printbuffer[100];
va_start (args, fmt);
For this to work, printbuffer must be larger than
* anything we ever want to print.
i = vsprintf (printbuffer, fmt, args);//对输入的参数进行格式整理
va_end (args);
puts (printbuffer); //调用上一章我们封装的puts函数实现向串口打印书字符串
}
「main.c」该文件可以直接调用printf()函数来打印信息了。
void delay_ms(unsigned int num)
{
int i,j;
for(i=num; i>0;i--)
for(j=1000;j>0;j--)
;
}
* 裸机代码,不同于LINUX 应用层, 一定加循环控制
int main (void)
{
int i = 0;
while (1) {
printf("aaaaaaaaaaaaa");
delay_ms(500);
}
return 0;
}
「Makefile」
CROSS_COMPILE = arm-none-eabi-
NAME =gcd
CFLAGS=-mfloat-abi=softfp -mfpu=vfpv3 -mabi=apcs-gnu -fno-builtin -fno-builtin-function -g -O0 -c -I ./include -I ./lib
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
OBJS=./cpu/start.o ./driver/uart.o
./driver/_udivsi3.o ./driver/_divsi3.o ./driver/_umodsi3.o main.o ./lib/printf.o
#=============================================================================#
all: $(OBJS)
$(LD) $(OBJS) -T map.lds -o $(NAME).elf
$(OBJCOPY) -O binary $(NAME).elf $(NAME).bin
$(OBJDUMP) -D $(NAME).elf > $(NAME).dis
%.o: %.S
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.s
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -rf $(OBJS) *.elf *.bin *.dis *.o
Makefile、map.lds 参考《7. 从0开始学ARM-GNU伪指令、代码编译,lds使用》
后续我们都会在这个模板上来编写其他硬件的驱动代码。