ARM接口编程

  • 1. GPIO编程
  • 1.1 学会看原理图和手册
  • 1.2 蜂鸣器驱动汇编版本
  • 1.3 编译和运行
  • 1.4 蜂鸣器驱动C语言版本
  • 1.5 编译和运行
  • 2. 串口编程
  • 2.1 并行和串行接口介绍
  • 2.1 串口电路原理图和手册
  • 2.2 串口编程
  • 2.3 编译和测试
  • 2.4 从0实现printf函数
  • 2.5 编译和运行
  • 3. Nand Flash编程
  • 3.1 Nand Flash管脚定义
  • 3.2 Nand Flash地址结构图
  • 3.3 Nand Flash电路原理图和手册
  • 3.4 Nand Flash编程实现初始化
  • 3.5 编译和测试



SOC最终需要与外设进行通信,SOC外面有很多接口,通过接口对外设进行读写。


接口编程分三类:

  • 一:是单个管脚的,自己模拟时序的,称为GPIO编程。
  • 二:是串行协议编程,如串口编程。
  • 三:是带协议的并行接口,如nandflash编程

1. GPIO编程

GPIO就是芯片上的那些干啥都行的引脚,这时就需要一个控制器来管理这些引脚,通过它来配置各个引脚的功能,即GPIO控制器。下面以操作蜂鸣器为例

1.1 学会看原理图和手册

首先,查看蜂鸣器电路原理图

arm gpu作用_#define


由图,只需在XpwmTOUCH1输出高电平,则三极管处导通,有源蜂鸣器就能发声。其次,查看核心板原理图

arm gpu作用_arm_02


得到XpwmTOUCH1接到GPD0_1最后,查看数据手册

arm gpu作用_arm_03


得到控制GPD0_1的寄存器地址后,就可以进行编程了

1.2 蜂鸣器驱动汇编版本

start.s:

.gloabal _start
_start:
		bl buzzer_init
for:
		bl buzzer_on
		@printf("buzzer on\n")
		ldr r0, =str_on
		mov lr, pc
		ldr pc, =0x3ff13e54
		bl delay
		
		bl buzzer_off
		@printf("buzzer off\n")
		ldr r0, =str_off
		mov lr, pc
		ldr pc, =0x3ff13e54
		bl delay
		
		b for

buzzer_init:
		@GPD0CON
		ldr r0, =0xE02000A0
		ldr r1, [r0]
		bic r1, r1, #(0xf<<4) @4~7=0000
		orr r1, r1, #(0x1<<4) @4~7=0001
		str r1, [r0]
		
		mov pc, lr
		
buzzer_on:
		@GPD0CON
		ldr r0, =0xE02000A4
		ldr r1, [r0]
		orr r1, r1, #(0x1<<1) @1=1
		str r1, [r0]
		
		mov pc, lr
		
buzzer_off:
		@GPD0CON
		ldr r0, =0xE02000A4
		ldr r1, [r0]
		bic r1, r1, #(0x1<<1) @1=0
		str r1, [r0]
		
		mov pc, lr

delay:
		mov r0, #0x100000
d:
		subs r0, r0, #1
		bne d
		mov pc, lr
		
str_on:
		.asciz "buzzer on\n"
		
str_off:
		.asciz "buzzer off\n"

1.3 编译和运行

arm gpu作用_arm gpu作用_04


arm gpu作用_arm_05

1.4 蜂鸣器驱动C语言版本

start.s:

.global _start
_start:
		bl main

buzzer.c:

#define GPD0CON (*(volatile unsigned int *)0xE02000A0)
#define GPD0DAT (*(volatile unsigned int *)0xE02000A4)
void buzzer_init()
{
	GPD0CON=GPD0CON&~(0xf<<4);
	GPD0CON=GPD0CON|(0x1<<4);
	
}
void buzzer_on()
{
	GPD0DAT|=0x1<<1;
}
void buzzer_off()
{
	GPD0DAT&=~(0x1<<1);
}
void delay()
{
	int i=1000000;
	while(i--);

}
int main()
{
	int (*printf)(char *format,...)=(void*)0x3ff13e54;
	buzzer_init();
	while(1)
	{
		buzzer_on();
		printf("buzzer on\n");
		delay();
	
		buzzer_off();
		printf("buzzer off\n");
		delay();
	}
	return 0;
}

1.5 编译和运行

arm gpu作用_vim_06


arm gpu作用_arm_07

2. 串口编程

2.1 并行和串行接口介绍

arm gpu作用_arm_08


内存与DMC(内存控制器)之间是通过地址总线和数据总线连接,都是并行接口。

有些外设如传感器则不必要用总线(并行接口)来传输,用串行接口即可满足需求,串行接口有以下几种:

  • 串口:异步全双工,适用于双方都有独立的时钟,不需要时钟线的场景。缺点是一对一,且速度慢
  • IIC:同步半双工,适用于只需要一方提供时钟信号进行同步的,IIC是总线性质的,可以挂载多个设备通过软件寻址找到设备。缺点是半双工。
  • SPI:同步全双工,适用于只需要一方提供时钟信号可以挂载多个设备通过硬件寻址(CS片选线)找到设备。
  • USB:协议固定,高速。

2.1 串口电路原理图和手册

电路原理图

arm gpu作用_#define_09

SP3232EEN:电平转换芯片,为了避免串口长距离传输电阻增高导致电压不足。将3.3V的电平转为RS232(±15V)电平输出核心板原理图

arm gpu作用_vim_10


数据手册

GPIO

arm gpu作用_arm gpu作用_11


UART

arm gpu作用_#define_12

2.2 串口编程

uart.c

#define GPA0CON  (*(volatile unsigned int *)0xE0200000)

#define ULCON0   (*(volatile unsigned int *)0xE2900000)
#define ULON0    (*(volatile unsigned int *)0xE2900004)
#define UTRSTAT0 (*(volatile unsigned int *)0xE2900010)
#define UTXH0    (*(volatile unsigned int *)0xE2900020)
#define URXH0    (*(volatile unsigned int *)0xE2900024)
#define UBRDIV0  (*(volatile unsigned int *)0xE2900028)
#define UDIVSLOT0 (*(volatile unsigned int *)0xE290002C)
void uart_init()
{
	//GPIO配置
	GPA0CON &= ~0xff;
	GPA0CON |= 0x22;
	//UART配置传输格式
	//8位长,一个停止位,没有校验位
	ULCON0 = 0x3;
	//读写模式: 轮询模式
	UCON0 = 0x5;
	//波特率
	//DIV_VAL=(PCLK/(bps x 16))-1
	//34.8= 66*1000*1000/(115200*16)-1
	UBRDIV0 = 34;
	UDIVSLOT0=0xDFDD;
}
char _getc()
{
	char ch;
	//查询是否有有效数据
	while((UTRSTAT0 & 0x1)==0)
		;
	ch = URXH0;
	return ch;
}
void _putc(char ch)
{
	//查询是否可以发送数据 UTRSTAT0的第二位
	while((UTRSTAT0 & (0x1<<2)) == 0)
		;
	UTXH0 = ch;
	
}

main.c

int main()
{
	uart_init();
	_putc('h');
	_putc('e');
	_putc('l');
	_putc('l');
	_putc('o');
	_putc('\n');
	_putc('\r');
	_putc('h');
	_putc('e');
	_putc('l');
	_putc('l');
	_putc('o');
	_putc('\n');
	_putc('\r');
	char ch;
	while(1)
	{
		ch = _getc();
		_putc('=');
		_put(ch);
	}
	return 0;
]

start.s:

.global _start
_start:
		bl main

2.3 编译和测试

arm gpu作用_vim_13


arm gpu作用_#define_14

2.4 从0实现printf函数

修改uart.c

#define GPA0CON  (*(volatile unsigned int *)0xE0200000)

#define ULCON0   (*(volatile unsigned int *)0xE2900000)
#define ULON0    (*(volatile unsigned int *)0xE2900004)
#define UTRSTAT0 (*(volatile unsigned int *)0xE2900010)
#define UTXH0    (*(volatile unsigned int *)0xE2900020)
#define URXH0    (*(volatile unsigned int *)0xE2900024)
#define UBRDIV0  (*(volatile unsigned int *)0xE2900028)
#define UDIVSLOT0 (*(volatile unsigned int *)0xE290002C)
void uart_init()
{
	//GPIO配置
	GPA0CON &= ~0xff;
	GPA0CON |= 0x22;
	//UART配置传输格式
	//8位长,一个停止位,没有校验位
	ULCON0 = 0x3;
	//读写模式: 轮询模式
	UCON0 = 0x5;
	//波特率
	//DIV_VAL=(PCLK/(bps x 16))-1
	//34.8= 66*1000*1000/(115200*16)-1
	UBRDIV0 = 34;
	UDIVSLOT0=0xDFDD;
}
char _getc()
{
	char ch;
	//查询是否有有效数据
	while((UTRSTAT0 & 0x1)==0)
		;
	ch = URXH0;
	return ch;
}
void _putc(char ch)
{
	//查询是否可以发送数据 UTRSTAT0的第二位
	while((UTRSTAT0 & (0x1<<2)) == 0)
		;
	UTXH0 = ch;
	
}
void _puts(char *str)
{
	while(*str != '\0')
	{
		_putc(*str);
		if(*str == '\n')
			_putc('\r');
		str++;
	}
}
#include <stdarg.h>
void itoa(int num,char *buf)
{
	int i;
	if(num < 10)
	{
		buf[0] = num+'0';
		buf[1] = '\0';
		
		return ;
	}
	itoa(num / 10,buf);
	for(i = 0; buf[i] != '\0';i++)
		;
	buf[i] = num % 10 + '0';
	buf[i+1] = '\0';
	return ;
}
void itox(int num,char *buf)
{
	int i;
	if(num < 16)
	{
		if(num < 10)
		{
			buf[0] = num + '0';
			buf[1] = '\0';
		}
		else
		{
			buf[0] = num - 10 + 'a';
			buf[1] = '\0';
		}
		return ;
	}
	itox(num / 16,buf);
	for(i = 0;buf[i] != '\0';i++)
		;
	if(num % 16 < 10)
	{
		buf[i] = num % 16 + '0';
		buf[i+1] = '\0';
	}
	else
	{
		buf[i] = num %16 - 10 +‘a';
		buf[i+1] = '\0';	
	}
	return ;
}
int u_printf(const char *format,...)
{
	va_list list;//系统提供的宏
	
	int n;
	char buf[128];
	char *s;
	
	va_start(list,format);//初始化宏
	
	while(*format != '\0')
	{
		if(*format == '%')
		{
			format++;
			switch(*format)
			{
				case 'd':
						n = va_arg(list,int)//取可变参数列表中的值
						itoa(n,buf);
						_puts(buf);
						break;
				case 'x':
						n = va_arg(list,int)
						itox(n,buf);
						_puts(buf);
						break;
				case 'c':
						n = va_arg(list,int)
						_putc(n);
						break;
				case 's':
						s = va_arg(list,char *);
						_puts(s);
						break;
				
			}
			format++;//跳过逗号
		}
		else
		{
			_putc(*format);
			if(*format == '\n')
				_putc('\r');
			format++;
			
		}
	}
	va_end(list);
	return 0;
}

main.c

int main()
{
	uart_init();
	_puts("hello world\n");
	_puts("hello world\n");
	_puts("hello world\n");
	_puts("hello world\n");
	u_printf("hello a = %d, c = %c, x = %x, s = %s\n",99,'c',0x11,"world");
	char ch;
	while(1)
	{
		ch = _getc();
		_putc('=');
		_put(ch);
	}
	return 0;
]

start.s

.global _start
_start:
		bl main

2.5 编译和运行

arm gpu作用_arm gpu作用_15


arm gpu作用_#define_16

3. Nand Flash编程

Nand Flash在嵌入式系统中的地位与PC上的硬盘类似。用于保存数据。与内存掉电后数据丢失不同,NandFlash中的数据在掉电后仍可永久保存。

不能随机写入,必须先擦除再写入的存储器一般称为ROM
ROM有两种:
1.读写以字节为单位,容量小,称为EEPROM
2.读写以页为单位,擦除以块为单位,被称为Flash
Flash也分两类:
- Nor Flash 接口像内存一样,有地址总线和数据总线CPU可以直接访问,和CPU总线共同编址容量要考虑CPU的地址总线
- Nand Flash 没有地址总线和数据总线,通过通用IO管脚访问,CPU不能直接访问,和CPU总线独立编址容量可以非常大

3.1 Nand Flash管脚定义

查看Nand Flash数据手册

arm gpu作用_arm开发_17


arm gpu作用_arm开发_18


N.C:不用连接,起固定作用和考虑到兼容性

I/O:数据、地址、指令都是通过IO管脚发送

ALE 0 CLE 1:拉高表示传输的是命令

ALE 1 CLE 0: 拉高表示传输的是地址

ALE0 CLE 0:表示传输的是数据

CE:片选信号线 低电平有效

RE:读使能

WE:写使能

WP:写保护使能

RB:状态管脚,判断是否准备好数据,低电平表示忙

3.2 Nand Flash地址结构图

arm gpu作用_#define_19


device就相当于书架,block相当于书,page相当于页

1 device = 8192 block

1 block = 64 page

1 page = 2K + 64

1 device = 1GB = 2的30次方因此一个地址是30位的,而IO一次只能传8位,所以分为5个周期传一条完整的地址,如下图所示:

arm gpu作用_arm_20


页内地址 = addr % 2048

页地址 = addr / 2048

3.3 Nand Flash电路原理图和手册

电路原理图和SOC连接图

arm gpu作用_arm gpu作用_21


Flash控制寄存器数据手册

arm gpu作用_#define_22

由NandFlash手册里需要的时序分析得到Flash控制寄存器里应该要配置的值

arm gpu作用_vim_23


计算具体值

arm gpu作用_arm gpu作用_24

3.4 Nand Flash编程实现初始化

main.c

#define NFCONF (*(volatile unsigned int *)0xB0E00000)
#define NFCONT (*(volatile unsigned int *)0xB0E00004)
#define NFCMMD (*(volatile unsigned int *)0xB0E00008)
#define NFADDR (*(volatile unsigned int *)0xB0E0000C)
#define NFDATA (*(volatile unsigned int *)0xB0E00010)
#define NFSTAT (*(volatile unsigned int *)0xB0E00028)

void nand_init()
{
	//配置寄存器:时序
#define TACLS 0
#define TWRPH0 2
#define TWRPH1 0
	NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(1<<1);
	//开启flash控制器
	NFCONT |= 0x1;
	//选住NandFlash
	NFCONT &= ~(0x1<<1)
	
}
void nand_read(unsigned int ddr,unsigned int nand,unsigned int len)
{
	int i = 0;
	unsigned int page_addr = nand / 2048;
	NFCMMD = 0x00;
	
	NFADDR = 0;
	NFADDR = 0;
	NFADDR = page_addr & 0xff;
	NFADDR = (page_addr>>8)&0xff;
	NFADDR = (page_addr>>16)&0xff;
	
	NFCMMD = 0x30;
	while((NFSTAT & 0x1) == 0)
		;
	//读2k数据
	for(i = 0;i < 512; i++,ddr +=4)
	{
		*(unsigned int *)ddr = NFDATA;//每次取四个字节
	}
	
}
int main()
{
	char buf[128];
	
	uart_init();
	
	nand_init();
	//buf内存地址,0x600000是nandflash里的地址 2048是长度
	nand_read((unsigned int)buf,0x600000,2048);
	
	buf[30] = '\0';
	
	uprintf(”%s\n",buf);
	
	return 0;
]

3.5 编译和测试

arm gpu作用_arm_25


内存中已有数据:

arm gpu作用_#define_26


从内存写到Flash:

arm gpu作用_vim_27

测试程序从Flash中读出来的结果:

arm gpu作用_arm gpu作用_28