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 学会看原理图和手册
首先,查看蜂鸣器电路原理图
由图,只需在XpwmTOUCH1输出高电平,则三极管处导通,有源蜂鸣器就能发声。其次,查看核心板原理图
得到XpwmTOUCH1接到GPD0_1最后,查看数据手册
得到控制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 编译和运行
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 编译和运行
2. 串口编程
2.1 并行和串行接口介绍
内存与DMC(内存控制器)之间是通过地址总线和数据总线连接,都是并行接口。
有些外设如传感器则不必要用总线(并行接口)来传输,用串行接口即可满足需求,串行接口有以下几种:
- 串口:异步全双工,适用于双方都有独立的时钟,不需要时钟线的场景。缺点是一对一,且速度慢。
- IIC:同步半双工,适用于只需要一方提供时钟信号进行同步的,IIC是总线性质的,可以挂载多个设备,通过软件寻址找到设备。缺点是半双工。
- SPI:同步全双工,适用于只需要一方提供时钟信号,可以挂载多个设备,通过硬件寻址(CS片选线)找到设备。
- USB:协议固定,高速。
2.1 串口电路原理图和手册
电路原理图
SP3232EEN:电平转换芯片,为了避免串口长距离传输电阻增高导致电压不足。将3.3V的电平转为RS232(±15V)电平输出核心板原理图
数据手册
GPIO
UART
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 编译和测试
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 编译和运行
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数据手册
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地址结构图
device就相当于书架,block相当于书,page相当于页
1 device = 8192 block
1 block = 64 page
1 page = 2K + 64
1 device = 1GB = 2的30次方因此一个地址是30位的,而IO一次只能传8位,所以分为5个周期传一条完整的地址,如下图所示:
页内地址 = addr % 2048
页地址 = addr / 2048
3.3 Nand Flash电路原理图和手册
电路原理图和SOC连接图
Flash控制寄存器数据手册
由NandFlash手册里需要的时序分析得到Flash控制寄存器里应该要配置的值
计算具体值
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 编译和测试
内存中已有数据:
从内存写到Flash:
测试程序从Flash中读出来的结果: