IIC测试程序,使用AT24C02存储芯片来测试的。
程序功能比较完善,有随意读,写,连续读,写,是使用IIC控制器,再加上一堆寄存器来完成的。
at24c02.c文件
-
#include "iic.h"
-
#include "at24c02.h"
-
#include "uart.h"
-
#include "string.h"
-
#include "interrupt.h"
-
#include "init.h"
-
-
/*
-
* 函数功能 : 从at24c02芯片中,指定的一个地址处,读取一个字节数据
-
* 函数参数 : address : at24c02芯片的地址
-
* 函数返回值 : 该地址处的数据
-
* 注意 :
-
* 有看数据手册at24xx,第3页,可知,AT24C02A是256字节*8bit=2Kbit的芯片,容量是256字节
-
* 使用8位地址模式,就可以寻址到所有的AT24C02A芯片的存储空间。
-
* 第8页,可知,AT24C02A固化地址前面7位固定是1|0|1|0|A2|A1|A0|
-
* 第8位,0 : 写, 1 :读
-
* AT24C02A在TQ2440原理图中,A0,A1,A2是0,0,0,
-
* 所以AT24C02A,作为从机,从机地址是0xA0
-
*/
-
unsigned char at24cxx_read(unsigned char address)
-
{
-
unsigned char val;
-
-
iic_write(0xA0, &address, 1);
-
iic_read(0xA1, (unsigned char *)&val, 1);
-
-
return val;
-
}
-
-
/*
-
* 函数功能 : 向at24c02芯片中,指定的一个地址处,写入一个字节的数据
-
* 函数参数 :
-
* address : at24c02芯片的地址
-
* data : 待写入的数据
-
* 注意 : 本函数所说的地址address, 是芯片内部的地址,不是芯片的固化地址。
-
*/
-
void at24cxx_write(unsigned char address, unsigned char data)
-
{
-
unsigned char val[2];
-
-
val[0] = address; //芯片内部的地址
-
val[1] = data;
-
//AT24C02A固化地址:0xA0
-
//uart_printf("at24cxx_write address = [%d] data = [%d]\n", address, data);
-
iic_write(0xA0, val, 2);
-
//uart_printf("at24cxx_write address ok\n");
-
}
at24c02.h文件
-
#ifndef _AT24C02_H_
-
#define _AT24C02_H_
-
-
unsigned char at24cxx_read(unsigned char address);
-
void at24cxx_write(unsigned char address, unsigned char data);
-
-
#endif
def.h文件
-
#ifndef __DEF_H__
-
#define __DEF_H__
-
-
#define U32 unsigned int
-
#define U16 unsigned short
-
#define S32 int
-
#define S16 short int
-
#define U8 unsigned char
-
#define S8 char
-
-
#endif
head.S文件
-
@******************************************************************************
-
@ File: head.S head.o init.o nand.o interrupt.o uart.o touchscreen.o main.o
-
@ 功能: 设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行
-
@******************************************************************************
-
.text
-
.global _start
-
_start:
-
@******************************************************************************
-
@ 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
-
@******************************************************************************
-
b Reset
-
-
@ 0x04: 未定义指令中止模式的向量地址
-
HandleUndef:
-
b HandleUndef
-
-
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
-
HandleSWI:
-
b HandleSWI
-
-
@ 0x0c: 指令预取终止导致的异常的向量地址
-
HandlePrefetchAbort:
-
b HandlePrefetchAbort
-
-
@ 0x10: 数据访问终止导致的异常的向量地址
-
HandleDataAbort:
-
b HandleDataAbort
-
-
@ 0x14: 保留
-
HandleNotUsed:
-
b HandleNotUsed
-
-
@ 0x18: 中断模式的向量地址
-
b HandleIRQ
-
-
@ 0x1c: 快中断模式的向量地址
-
HandleFIQ:
-
b HandleFIQ
-
-
Reset:
-
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
-
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
-
bl clock_init @ 设置MPLL,改变FCLK、HCLK、PCLK
-
bl memsetup @ 设置存储控制器以使用SDRAM
-
bl nand_init @ 初始化NAND Flash
-
@******************************************************************************
-
@ 注意 : 代码的运行地址,并非代码的连接地址,所以上面的函数 disable_watch_dog clock_init memsetup nand_init
-
@ 上面的函数中,都不能有全局变量,因为全局变量和static变量一起,放在bss段中,大于4K地址范围了,bl是4K内可以跳转,所以函数使用全局变量会失败
-
@******************************************************************************
-
@ 复制nand中4k后的代码到SDRAM中
-
ldr r0, =_start @ 1. 目标地址 = 0x30000000,这是SDRAM的起始地址
-
mov r1, #0 @ 2. 源地址 = 0,代码保存在NAND Flash 0地址开始处
-
ldr r2, =__bss_start
-
sub r2, r2, r0 @ 3. 复制长度
-
bl CopyCode2SDRAM @ 调用C函数CopyCode2SDRAM
-
-
bl clean_bss @ 清除bss段,未初始化或初值为0的全局/静态变量保存在bss段
-
-
msr cpsr_c, #0xd2 @ 进入中断模式
-
ldr sp, =0x31000000 @ 设置中断模式栈指针
-
-
msr cpsr_c, #0xdf @ 进入系统模式
-
ldr sp, =0x34000000 @ 设置系统模式栈指针,
-
/*
-
ldr lr, =ret_inituart @ 设置返回地址
-
ldr pc, =init_uart @ 这里为什么不能用 bl init_uart ???,也可以在main函数里面初始化
-
ret_inituart:
-
-
-
ldr lr, =ret_initirq @ 设置返回地址
-
ldr pc, =init_irq @ 调用中断初始化函数
-
ret_initirq:
-
*/
-
msr cpsr_c, #0x5f @ 设置I-bit=0,开IRQ中断
-
-
ldr lr, =halt_loop @ 设置返回地址
-
ldr pc, =main @ 调用main函数
-
halt_loop:
-
b halt_loop
-
-
HandleIRQ:
-
sub lr, lr, #4 @ 计算返回地址
-
stmdb { r0-r12,lr } @ 保存使用到的寄存器
-
@ 注意,此时的sp是中断模式的sp
-
@ 初始值是上面设置的4096
-
-
ldr lr, =int_return @ 设置调用IRQ_Handle函数后的返回地址
-
ldr pc, =EINT_Handle @ 调用中断分发函数,在interrupt.c中
-
int_return:
-
ldmia { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
iic.c文件
-
/*
-
* FILE: i2c.c
-
* 用于主机发送/接收
-
*/
-
-
#include "iic.h"
-
#include "uart.h"
-
#include "string.h"
-
#include "interrupt.h"
-
#include "init.h"
-
-
static S3C2440_IIC s3c2440_iic;
-
-
/*
-
*功能:延时函数
-
*/
-
static void wait(volatile unsigned int num)
-
{
-
unsigned int i = 0;
-
unsigned int j = 0;
-
for(i=0; i<1000; i++)
-
for(j=0; j<num; j++);
-
}
-
-
/*
-
* IIC初始化
-
*/
-
void iic_init(void)
-
{
-
/*设置GPIO相关引脚为 IIC引脚*/
-
GPEUP |= 0xc000; // 禁止内部上拉
-
GPECON |= 0xa0000000; // 选择引脚功能:GPE15:IICSDA 数据线, GPE14:IICSCL 时钟线
-
-
/*设置中断屏蔽寄存器,一级中断
-
*INTMSK[27]:0使能/1禁止 INT_IIC
-
*/
-
INTMSK &= ~(BIT_IIC);
-
-
/*多主机 IIC总线控制(IICCON)寄存器
-
* bit[7] = 1, 使能ACK
-
* bit[6] = 0, IICCLK = PCLK/16
-
* bit[5] = 1, 使能IIC发送/接收中断
-
* bit[3:0] = 0xf, Tx clock = IICCLK/(15+1)
-
* PCLK = 50MHz, IICCLK = 3.125MHz, 发送器时钟 Tx Clock = 0.195MHz
-
*/
-
IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf
-
-
/*多主机 IIC总线地址(IICADD)寄存器
-
* bit[7:1] = 1, 使能ACK
-
*/
-
IICADD = 0x10; // S3C2440 从机地址 = [7:1]
-
-
/*多主机 IIC总线控制/状态(IICSTAT)寄存器
-
* bit[7:6] = 00, 从机接收器模式
-
* bit[5] = 0, 总线空闲
-
* bit[4] = 0, 使能发送/接收功能
-
* bit[3:0] = 0xf, Tx clock = IICCLK/(15+1)
-
*/
-
IICSTAT = 0x10; // I2C串行输出使能(Rx/Tx)
-
-
request_irq(ISR_IIC_OFT, iic_interrupt); /*安装IIC中断处理函数*/
-
}
-
-
/*
-
* 函数功能 : 主机发送
-
* 函数参数 :
-
* slv_addr : 从机地址
-
* buf : 数据存放的缓冲区
-
* len : 数据长度
-
* 注意 : 操作步骤,查看芯片手册at24xx,第9页,Figure 2 : 任意写。Figure 3 : 页写。
-
*/
-
void iic_write(unsigned int slv_addr, unsigned char *buf, int len)
-
{
-
uart_printf("iic_write address buf[0]=[%d], buf[1]=[%d]\n", buf[0], buf[1]);
-
s3c2440_iic.mode = WRDATA; //写操作
-
s3c2440_iic.date_offset = 0; //索引值初始为0
-
s3c2440_iic.data = buf; //保存缓冲区地址
-
s3c2440_iic.data_len = len; //传输长度
-
-
/*在IICSTAT[4]=1时,才可以写入将要发送的数据,这里是数据是IIC芯片的固化地址0xA0*/
-
IICDS = slv_addr;
-
/*
-
此时,没有中断发生,IICCON[4]读取出来应该是0,
-
IICSTAT[7:6]=11,主机发送器模式,
-
令IICSTAT[5:4]=11,将发出S信号,之后IICDS中的地址数据也将自动发送,用来寻址从机。
-
此时,将从IIC总线锁存7位从机地址到 IICADD寄存器。
-
*/
-
IICSTAT = 0xf0;
-
/*此时,已经启动IIC传输,并且是主机发送器模式。另外IICCON[5]=1,表示已经使能中断了。
-
*当发送地址信息到一个从机地址并吻合,收到ACK信号时,即响应周期之后,IIC中断将会发生。
-
*一旦中断发生,IICCON[4]将等于1,IIC将停止传输。
-
*/
-
-
/* 等待直至数据传输完毕 */
-
while(1)
-
{
-
uart_printf("\n iic_write s3c2440_iic.data_len = [%d]\n", s3c2440_iic.data_len);
-
if(s3c2440_iic.data_len == -1)
-
{
-
uart_printf("\n iic_write s3c2440_iic.data_len = [%d]\n", s3c2440_iic.data_len);
-
break;
-
}
-
}
-
uart_printf("iic_write ok\n");
-
}
-
-
/*
-
* 函数功能 : 主机接收
-
* 函数参数 :
-
* slv_addr : 从机地址
-
* buf : 数据存放的缓冲区
-
* len : 数据长度
-
* 注意 : 操作步骤,查看芯片手册at24xx,第9页,Figure 5 : 任意读。Figure 6 : 连续读。
-
*/
-
void iic_read(unsigned int slv_addr, unsigned char *buf, int len)
-
{
-
s3c2440_iic.mode = RDDATA; //读操作
-
s3c2440_iic.date_offset = -1; //索引值初始化为-1,表示第1个中断时不接收数据(地址中断)
-
s3c2440_iic.data = buf; //保存缓冲区地址
-
s3c2440_iic.data_len = len; //传输长度
-
-
IICDS = slv_addr; //IIC芯片的固化地址0xA0
-
/*
-
IICSTAT[7:6]=10,主机接收器模式,
-
令IICSTAT[5:4]=11,将发出S信号,之后IICDS中的固化地址数据也将自动发送,用来寻址从机。
-
此时,将从IIC总线锁存7位从机地址到 IICADD寄存器。
-
*/
-
IICSTAT = 0xb0; // 主机接收,启动
-
-
/* 等待直至数据传输完毕 */
-
while(1)
-
{
-
uart_printf("\n iic_read s3c2440_iic.data_len = [%d]\n", s3c2440_iic.data_len);
-
if(s3c2440_iic.data_len == 0)
-
{
-
uart_printf("\n iic_read s3c2440_iic.data_len = [%d]\n", s3c2440_iic.data_len);
-
break;
-
}
-
}
-
uart_printf("iic_read ok\n");
-
}
-
-
/*
-
* IIC中断服务程序 : 支持页写,连续读
-
* 根据剩余的数据长度选择继续传输或者结束
-
* 注意 : 操作步骤,查看芯片手册at24xx,第9页,Figure 2 : 任意写。Figure 3 : 页写。
-
* 注意 : 操作步骤,查看芯片手册at24xx,第9页,Figure 5 : 任意读。Figure 6 : 连续读。
-
* 问题 : 如果不使用全局变量,如何区分是哪种情况下发生的中断???
-
*/
-
void iic_interrupt(void)
-
{
-
unsigned long iic_stat; /*保存寄存器IICSTAT的值*/
-
-
//清中断(中断处理前:最好清除所有的挂起寄存器; 完成之后:必须清除所有的挂起寄存器)
-
SRCPND = BIT_IIC;
-
INTPND = BIT_IIC;
-
iic_stat = IICSTAT;
-
-
/*
-
IICSTAT[4]=0,表示总线仲裁成功,即本次传输抢到总线,因为只有一个主机,所以每次都可以抢到总线。
-
*/
-
//uart_printf("IICSTAT=[%x]\n", IICSTAT);
-
if(iic_stat & 0x8)
-
{
-
uart_printf("Bus arbitration failed\n\r");
-
}
-
else //IICSTAT[4]=0,本次传输抢到总线
-
{
-
switch(s3c2440_iic.mode)
-
{
-
case WRDATA: //写模式
-
{
-
uart_printf("s3c2440_iic.mode=[WRDATA]\n");
-
if(s3c2440_iic.data_len == 0) //想发送的数据已经写完了
-
{
-
//uart_printf("s3c2440_iic.data_len=[%d]\n", s3c2440_iic.data_len);
-
s3c2440_iic.data_len--; //写函数iic_write还在循环等待这个值变小。
-
//下面两行用来恢复(唤醒)I2C操作,发出P信号
-
/*
-
此时,有中断发生,IICCON[4]读取出来应该是1,停止所有的IIC传输。
-
IICSTAT[7:6]=11,主机发送器模式,令IICSTAT[5:4]=00,将发出P信号。
-
*/
-
IICSTAT = 0xd0;
-
/*虽然本次写数据已经完成,但是最后,还是要向IICCON[4]=0,恢复刚才暂停的IIC操作,
-
以便可以继续传输数据,
-
*/
-
IICCON = 0xaf;
-
wait(100); //等待一段时间以便P信号已经发出
-
break;
-
}
-
//走到这里说明:想发送的数据,还没有发送完,继续将data中的下一个数据,保存到IICDS。
-
//uart_printf("s3c2440_iic.date_offset=[%d]\n", s3c2440_iic.date_offset);
-
IICDS = s3c2440_iic.data[s3c2440_iic.date_offset];
-
//uart_printf("IICDS=[%d]\n", IICDS);
-
s3c2440_iic.date_offset++;
-
-
//将数据写入IICDS后,需要一段时间才能出现在SDA线上
-
wait(100);
-
/*此时,IICCON[4]读取出来应该是1,停止所有的IIC传输。
-
2440此时作为发送器,已经将要发送的数据写入IICDS寄存器中,
-
向IICCON[4]写0,即可恢复IIC操作,即可立马发送 IICDS中的数据。
-
当发送完一个字节的数据之后,又会产生新的IIC中断了。
-
IICCON[3:0]写f,是位了保存在IIC初始化函数中设置的值一致。
-
*/
-
IICCON = 0xaf; //恢复I2C传输
-
s3c2440_iic.data_len--;
-
break;
-
}
-
case RDDATA: //读模式
-
{
-
uart_printf("s3c2440_iic.mode=[RDDATA]\n");
-
if(s3c2440_iic.date_offset == -1)
-
{
-
//这次中断的原因:2440发送IIC设备的固化地址后发生的
-
//只接收一个数据时,不要发出ACK信号
-
s3c2440_iic.date_offset = 0;
-
if(s3c2440_iic.data_len == 1) //只读取最后一位数据了
-
{
-
/*
-
因为此时2440是主机接收器模式,IICCON[7]写0:表示2440在接收数据完成之后,不发送ACK信号
-
向IICCON[4]写0,即可恢复IIC操作。
-
因为此时2440是主机接收器模式,所以,可以从IICDS寄存器中读取接收到的数据。
-
*/
-
IICCON = 0x2f; //恢复I2C传输,开始接收数据,接收到数据后不发出ACK
-
}
-
else
-
{
-
/*
-
因为此时2440是主机接收器模式,IICCON[1]写0:表示2440在接收数据完成之后,发送ACK信号,2440可以连续读。
-
向IICCON[4]写0,即可恢复IIC操作。
-
因为此时2440是主机接收器模式,所以,可以从IICDS寄存器中读取接收到的数据。
-
*/
-
IICCON = 0xaf; //恢复I2C传输,开始接收数据
-
}
-
break;
-
}
-
/*从IICDS寄存器中读取接收到的数据*/
-
s3c2440_iic.data[s3c2440_iic.date_offset] = IICDS;
-
s3c2440_iic.date_offset++;
-
-
s3c2440_iic.data_len--;
-
if(s3c2440_iic.data_len == 0) //所有数据都读取完成
-
{
-
//下面两行恢复(唤醒)I2C操作,发出P信号
-
/*
-
此时,有中断发生,IICCON[4]读取出来应该是1,停止所有的IIC传输。
-
IICSTAT[7:6]=10,主机接收器模式,令IICSTAT[5:4]=00,将发出P信号。
-
*/
-
IICSTAT = 0x90;
-
/*向IICCON[4]写0,即可恢复IIC操作。此时在主机接收器模式,以便可以发出P信号。*/
-
IICCON = 0xaf;
-
wait(100); //等待一段时间以便P信号已经发出
-
break;
-
}
-
else //数据还未读取完成
-
{
-
if(s3c2440_iic.data_len == 1) //接收最后一个数据时,不要发出ACK信号
-
{
-
IICCON = 0x2f; // 恢复(唤醒)I2C传输,接收到下一数据时无ACK
-
}
-
else //接收的不是最后一个数据时,要发出ACK信号,以便继续接收下一个数据。
-
{
-
IICCON = 0xaf; // 恢复(唤醒)I2C传输,接收到下一数据时发出ACK
-
}
-
}
-
break;
-
}
-
default:
-
break;
-
}
-
}
-
}
-
-
-
-
/*
-
芯片介绍:
-
AT24C02 : 是具有IIC接口的EEPROM,大小是:256字节*8位=2K bit,
-
所以 AT24C02的内部地址只用8根地址线即可全部寻址。
-
-
AT24C04 : 4k的EEPROM,大小是:512字节*8位=4K bit,
-
所以 AT24C04的内部地址要用9根地址线才可全部寻址。
-
AT24C04的固话地址的低[1]是P0,相对于是第9根地址线了,
-
这样EEPROM就会根据P0=0,选择页0, P1=0,选择页1。
-
-
AT24C08 有P0,P1。 AT24C16 有P0,P1,P2 同理,可以寻址多个页。
-
每个页的空间大小是256字节。
-
-
图解4 :
-
CURRENT ADDRESS READ:
-
The internal data word address counter maintains the last address accessed during the last read
-
or write operation, incremented by one.
-
This address stays valid between operations as long as the chip power is maintained.
-
The address “roll over” during read is from the last byte of the last memory page to the first byte of the first page.
-
The address “roll over” during write is from the last byte of the current page to the first byte of the same page.
-
-
Once the device address with the read/write select bit set to one is clocked in and acknowledged by the EEPROM, the current address data word is serially clocked out.
-
The microcontroller does not respond with an input zero but does generate a following stop condition (refer to Figure 4).
-
-
内部数据字地址计数器保持在过去的读或写操作时,递增一最后一次访问的地址。
-
这个地址在操作之间有效,只要芯片功率保持。
-
在读地址“翻转”是从存储器的最后页到第一页的第一个字节的最后一个字节。
-
写在地址“翻转”是从当前页的同一页的第一个字节的最后一个字节。
-
一旦与读取设备地址/写选择位为1的时钟并得到EEPROM应答后,当前地址的数据字就会串行输出。
-
该微控制器与一个不返回应答信号,而是产生一个紧随的停止条件(参见图4)。
-
-
-
比如,图解5,最后2440不发出一个ACK回应,不发出Stop命令,
-
直接来图解4,发出START命令,读取的是下一个递增的地址。
-
另外,图解5,最后2440给出一个ACK回应,不发出Stop命令,
-
直接来图解6,读取的也是下一个递增的地址。
-
之所以叫当前地址读,刚才操作的地址的下一个地址,就是当前地址.
-
-
*/
iic.h文件
-
#ifndef _IIC_H_
-
#define _IIC_H_
-
-
/* IIC 相关的 GPIO 寄存器 */
-
#define GPECON (*(volatile unsigned long *)0x56000040)
-
#define GPEDAT (*(volatile unsigned long *)0x56000044)
-
#define GPEUP (*(volatile unsigned long *)0x56000048)
-
-
/* IIC 相关的寄存器 */
-
#define IICCON (*(volatile unsigned long *)0x54000000) //多主机 IIC总线控制(IICCON)寄存器
-
#define IICSTAT (*(volatile unsigned long *)0x54000004) //多主机 IIC总线控制/状态(IICSTAT)寄存器
-
#define IICADD (*(volatile unsigned long *)0x54000008) //多主机 IIC总线地址(IICADD)寄存器
-
#define IICDS (*(volatile unsigned long *)0x5400000c) //多主机 IIC总线发送/接收数据移位(IICDS)寄存器
-
#define IICLC (*(volatile unsigned long *)0x54000010) //多主机 IIC总线线控制(IICLC)寄存器
-
-
void iic_init(void);
-
void iic_write(unsigned int slvAddr, unsigned char *buf, int len);
-
void iic_read(unsigned int slvAddr, unsigned char *buf, int len);
-
void iic_interrupt(void);
-
-
-
/*
-
因为IIC中断发生时,无法判断是哪种,没有哪一个寄存器说明是什么情况下发生的中断,
-
所以,需要一个全局的结构体来保存相关信息,以便在中断服务程序中判断是什么情况下产生的IIC中断。
-
*/
-
typedef struct iic_struct
-
{
-
unsigned char * data; /*数据缓冲区*/
-
volatile int data_len; /*等待传输的数据长度*/
-
volatile int stat; /*状态*/
-
volatile int mode; /*模式:读/写*/
-
volatile int date_offset; /*data中待传输数据的位置*/
-
}S3C2440_IIC;
-
-
#define WRDATA (1)
-
#define RDDATA (2)
-
-
#endif
iic.lds文件
-
SECTIONS { /*.:表示当前*/
-
. = 0x30000000; /*汇编语言中:.text段中_start的值=0x30000000*/
-
.text : { *(.text) }
-
-
. = ALIGN(4);
-
.rodata : {*(.rodata*)}
-
-
. = ALIGN(4);
-
.data : { *(.data) }
-
-
. = ALIGN(4);
-
__bss_start = .; /*编译出来的2进制程序比如uboot.bin中不会含有static变量初始化为0的存放的空间,这样二进制文件就小了。我们把static变量统一放在bss段,运行程序前,将bss段清0,即将static变量初始化为0了*/
-
.bss : { *(.bss) *(COMMON) } /*全局变量和static变量一起,放在bss段中*/
-
__bss_end = .;
-
}
-
-
-
/*
-
加上nandflash的其他函数之后, head.o init.o nand.o >4K 了,开发板只复制nand的前面4K到启动石。
-
使用下面的链接脚本会有局限性。
-
SECTIONS {
-
firtst 0x00000000 : { head.o init.o nand.o}
-
second 0x30001000 : AT(4096) { leds.o }
-
}
-
解决方法 :
-
1,如果非要使用上面的链接脚本,就必须把nand.c文件控制在很小范围内,
-
其他nand相关函数独立编写一个文件nand2.c,比如 :
-
SECTIONS {
-
firtst 0x00000000 : { head.o init.o nand.o}
-
second 0x30001000 : AT(4096) { leds.o nand2.o }
-
}
-
2,使用本文件使用的链接脚本,只要保证head.S中bl调用的少量函数(init_uart, nand_init, nand_read)在4K范围内,就可以了
-
整个.o文件可以完全大于4K范围。
-
*/
init.c文件
-
#include "init.h"
-
-
/*
-
* 关闭WATCHDOG,否则CPU会不断重启
-
*/
-
void disable_watch_dog(void)
-
{
-
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
-
}
-
-
/*
-
* 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
-
* 有如下计算公式:
-
* S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
-
* 其中: m = MDIV + 8 = 92+8, p = PDIV + 2 = 1+2, s = SDIV = 2
-
* 对于本开发板,Fin = 12MHz
-
* 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4,
-
* FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
-
*/
-
void clock_init(void)
-
{
-
// LOCKTIME = 0x00ffffff; // 使用默认值即可
-
CLKDIVN = 0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
-
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
-
__asm__(
-
"mrc p15, 0, r1, c1, c0, 0\n" /* 读出控制寄存器 */
-
"orr r1, r1, #0xc0000000\n" /* 设置为“asynchronous bus mode” */
-
"mcr p15, 0, r1, c1, c0, 0\n" /* 写入控制寄存器 */
-
);
-
-
MPLLCON = S3C2440_MPLL_200MHZ;
-
/* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
-
并不保证你的任何设置都是合适的。所以,如果想要工作在200MHz,还是按照vivi的推荐值((0x5c<<12)|(0x01<<4)|(0x02))即可。
-
*/
-
}
-
-
/*
-
s3c2440的bank6和bank7支持128M B,注意,单位是 Byte.
-
TQ2440原理图的HY57V561620FTP是 4bank * 4M * 16bit,256M,注意,单位是 bit,也就是 32M Byte.
-
U6+U7 : 共64M
-
*/
-
void memsetup(void)
-
{
-
volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
-
-
// 可生成”位置无关的代码”,使得这个函数可以在被复制到
-
// SDRAM之前就可以在steppingstone中运行
-
// 存储控制器13个寄存器的值
-
p[0] = 0x22011110; //BWSCON
-
p[1] = 0x00000700; //BANKCON0
-
p[2] = 0x00000700; //BANKCON1
-
p[3] = 0x00000700; //BANKCON2
-
p[4] = 0x00000700; //BANKCON3
-
p[5] = 0x00000700; //BANKCON4
-
p[6] = 0x00000700; //BANKCON5
-
p[7] = 0x00018005; //BANKCON6
-
p[8] = 0x00018005; //BANKCON7
-
-
// REFRESH,
-
// HCLK=12MHz: 0x008C07A3,
-
// HCLK=100MHz: 0x008C04F4
-
p[9] = 0x008C04F4;
-
p[10] = 0x000000B1; //BANKSIZE
-
p[11] = 0x00000030; //MRSRB6
-
p[12] = 0x00000030; //MRSRB7
-
}
-
-
/*
-
*清空bss段
-
*/
-
void clean_bss(void)
-
{
-
extern int __bss_start, __bss_end; /*这2个在链接脚本中定义的变量*/
-
int *p = &__bss_start;
-
-
for(; p < &__bss_end; p++)
-
{
-
*p = 0;
-
}
-
}
init.h文件
-
#ifndef _INIT_H_
-
#define _INIT_H_
-
-
//按键相关的GPIO
-
#define GPFCON (*(volatile unsigned long *)0x56000050)
-
#define GPFDAT (*(volatile unsigned long *)0x56000054)
-
#define GPFUP (*(volatile unsigned long *)0x56000058)
-
/*注意:这个程序没有mmu,所以寄存器的地址是物理地址*/
-
/**************************LED相关寄存器*******************************/
-
#define GPBCON (*(volatile unsigned long *)0x56000010)
-
#define GPBDAT (*(volatile unsigned long *)0x56000014)
-
#define GPBUP (*(volatile unsigned long *)0x56000018)
-
/*注意:这个程序没有mmu,所以寄存器的地址是物理地址*/
-
-
#define GPB5_OUT (1<<(5*2))
-
#define GPB6_OUT (1<<(6*2))
-
#define GPB7_OUT (1<<(7*2))
-
#define GPB8_OUT (1<<(8*2))
-
-
#define GPB5_ON (~(1<<5))
-
#define GPB6_ON (~(1<<6))
-
#define GPB7_ON (~(1<<7))
-
#define GPB8_ON (~(1<<8))
-
-
#define GPB5_OFF (1<<5)
-
#define GPB6_OFF (1<<6)
-
#define GPB7_OFF (1<<7)
-
#define GPB8_OFF (1<<8)
-
/*********************************************************/
-
-
/* WATCHDOG寄存器 */
-
#define WTCON (*(volatile unsigned long *)0x53000000)
-
/* SDRAM寄存器 */
-
#define MEM_CTL_BASE 0x48000000
-
#define SDRAM_BASE 0x30000000
-
#define GSTATUS1 (*(volatile unsigned long *)0x560000B0)
-
-
-
/*系统时钟相关寄存器*/
-
#define MPLLCON (*(volatile unsigned long *)0x4c000004)
-
#define CLKDIVN (*(volatile unsigned long *)0x4c000014)
-
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
-
-
void disable_watch_dog();
-
void clock_init(void);
-
void memsetup();
-
void clean_bss(void);
-
-
#endif
interrupt.c文件
-
#include "interrupt.h"
-
#include "uart.h"
-
#include "init.h"
-
-
/*定义一个函数指针数组 : 数组的每一个元素是一个函数指针,用来保存用户定义的中断处理函数*/
-
static void (*irq_interrupt_array[32])(void);
-
-
void init_irq_interrupt(void) /*没有安装中断函数的时候,使用这个函数来初始化中断服务函数*/
-
{
-
while(1);
-
}
-
-
/*函数功能:初始化函数指针数组
-
*/
-
void all_init_irq(void)
-
{
-
int i = 0;
-
for (i = 0; i < (sizeof(irq_interrupt_array) / sizeof(irq_interrupt_array[0])); i++)
-
{
-
irq_interrupt_array[i] = init_irq_interrupt;
-
}
-
}
-
-
/*
-
*函数功能:安装中断处理函数
-
*参数:
-
* num : 32个一级中断的编号,即中断号
-
* void (*irq_interrupt)(void) : 是个函数指针,是用户编写的中断处理函数。
-
*注意:
-
* 这个函数完整版是:用2个参数:1个表示1级中断,1个表示2级中断(没有2级中断时,用0)
-
*/
-
int request_irq(int num, void (*irq_interrupt)(void))
-
{
-
irq_interrupt_array[num] = irq_interrupt;
-
-
return 0;
-
}
-
-
/*
-
* 功能:初始化按键相关的GPIO引脚为外部中断
-
* 本程序:
-
* K1 : EINT1,引脚是GPF1
-
* K2 : EINT4,引脚是GPF4
-
* K3 : EINT2,引脚是GPF2
-
* K4 : EINT0,引脚是GPF0
-
*/
-
void init_irq(void)
-
{
-
GPFCON |= (GPF0_EINT | GPF1_EINT | GPF2_EINT | GPF4_EINT); /*设置这几个引脚为中断功能*/
-
GPFUP = 0x00;
-
-
EXTINT0 = 0x00; /*设置外部中断控制寄存器,低电平触发,默认也是0x00,所以这句可以不要*/
-
-
/*设置外部中断屏蔽寄存器,EINT4~EINT24,共20个需要在这个寄存器中使能他们,
-
*EINTMASK只能使能EINT4, EINT0,EINT1,EINT2,EINT3 不受EINTMASK控制
-
*EINTMASK[4]:禁止/使能EINT4
-
*/
-
EINTMASK &= (~(1<<4));
-
-
/*设置模式寄存器 :在IRQ模式中处理,默认也是0x00,所以这句可以不要*/
-
INTMOD = 0x00;
-
-
/*设置中断优先级寄存器
-
*仲裁租0和6,优先级不轮换使能,每个仲裁组下优先级顺序:0-1-2-3-4-5-6,默认也是这个顺序
-
*最终按键产生中断的优先级顺序:K4(EINT0) > K1(EINT1) > K3(EINT2) > K2(EINT4)
-
*/
-
PRIORITY &= (~(1<<0)) & (~(1<<6)) ;
-
-
/*设置中断屏蔽寄存器,一级中断
-
*INTMSK[0]:禁止/使能EINT0
-
*INTMSK[1]:禁止/使能EINT1
-
*INTMSK[2]:禁止/使能EINT2
-
*INTMSK[4]:禁止/使能EINT4~EINT7
-
*/
-
INTMSK &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4)); /*其实到这里,中断仍未完全开启,CPSR的I位还要清0*/
-
-
all_init_irq();
-
}
-
-
/*
-
*功能:中断服务子程序
-
*注意:1,按键产生中断的优先级顺序:K4(EINT0) > K1(EINT1) > K3(EINT2) > K2(EINT4)
-
* 所以,同时按下其中2个,3个或者4个,只有优先级最高的哪个LED会被点亮
-
* 2,如果硬件接线有LED连接了EINT4,EINT5,由于他们的中断优先级相同,EINTPEND[4:5]都是1,但都只会让SRCPND[4]=1,
-
* 如果他们同时按下,则INTPND[4]=1,且INTOFFSET=4,走到case 4,EINTPEND[4:5]=11b,2个if都成立,所以,最后这2个LED会被同时点亮。
-
*/
-
void EINT_Handle()
-
{
-
/*INTOFFSET:中断偏移寄存器,INTPND寄存器中的值被置1之后,中断偏移寄存器的值=32个一级中断的编号*/
-
unsigned long offset = INTOFFSET;
-
-
uart_printf("\n\nINTOFFSET(interrupt no.)=[%d]\n", offset);
-
-
irq_interrupt_array[offset](); //调用对应的irq中断服务函数
-
-
/*中断处理完成之后:必须清除所有的挂起寄存器
-
*注意:向挂起寄存器中写1,即可令此位为0;写入0是没有效果的,若写入0,挂起寄存器中的数据保持不变。
-
*/
-
if(offset == 4) /*二级外部中断,还必须清除 EINTPEND*/
-
{
-
EINTPEND = (1<<4);
-
}
-
SRCPND = (1<<offset);
-
INTPND = (1<<offset);
-
-
//uart_printf("SRCPND=[0x%x]\n", SRCPND);
-
//uart_printf("INTPND=[0x%x]\n", INTPND);
-
//uart_sendString("*******************************************\n");
-
/*在清除 SRCPND INTPND时,INTOFFSET会被自动清除*/
-
}
interrupt.h文件
main.c文件
-
#include "def.h"
-
#include "init.h"
-
#include "nand.h"
-
#include "uart.h"
-
#include "string.h"
-
#include "setup.h"
-
#include "iic.h"
-
#include "at24c02.h"
-
#include "interrupt.h"
-
-
/*如果在interupt.h的头文件中,没有申明下面的函数 init_irq,
-
只在interupt.c的文件中有定义,又想在本文件中使用函数init_irq,
-
就需要使用 extern 来申明一下,其他文件中有定义
-
extern void init_irq(void);
-
*/
-
-
/*
-
*功能:延时函数
-
*/
-
static void wait(volatile unsigned int num)
-
{
-
unsigned int i = 0;
-
unsigned int j = 0;
-
for(i=0; i<1000; i++)
-
for(j=0; j<num; j++);
-
}
-
-
int iic_test()
-
{
-
char get_ch;
-
char str[128];
-
int address = 0;
-
int data = 0;
-
-
iic_init();
-
-
while (1)
-
{
-
memset(str, 0, sizeof(str));
-
address = 0;
-
data = 0;
-
-
uart_printf("\r\n##### AT24C02 Menu #####\r\n");
-
uart_printf("[R] Read AT24C02\n\r");
-
uart_printf("[W] Write AT24C02\n\r");
-
uart_printf("Enter your selection: ");
-
-
get_ch = uart_getch();
-
uart_printf("%c\n\r", get_ch);
-
switch(get_ch)
-
{
-
case 'r':
-
case 'R':
-
{
-
uart_printf("Enter address: ");
-
memset(str, 0, sizeof(str));
-
uart_getString(str);
-
address = atoi(str);
-
uart_printf("\r\n read address = [%d]\r\n", address);
-
data = at24cxx_read(address);
-
uart_printf("data = [%d]\r\n", data);
-
uart_printf("read ok!\n");
-
break;
-
}
-
case 'w':
-
case 'W':
-
{
-
uart_printf("Enter address: ");
-
memset(str, 0, sizeof(str));
-
uart_getString(str);
-
address = atoi(str);
-
uart_printf("get str [%s]\r\n", str);
-
uart_printf("Enter data: ");
-
uart_getString(str);
-
data = atoi(str);
-
uart_printf("write address [%d] with data [%d]\r\n", address, data);
-
at24cxx_write(address, data);
-
uart_printf("write ok!\n");
-
break;
-
}
-
default:
-
break;
-
}
-
uart_printf("continue!\n");
-
}
-
return 0;
-
}
-
-
-
int main()
-
{
-
icache_enable();
-
init_irq();
-
init_uart();
-
iic_init();
-
-
uart_sendString("\n\n");
-
uart_sendString("My iictest start:\n\n");
-
-
GPBCON = (GPB5_OUT | GPB6_OUT | GPB7_OUT | GPB8_OUT);
-
GPBUP = 0x1e0;
-
-
iic_test();
-
-
while(1)
-
{
-
GPBDAT = (GPB5_ON & GPB6_ON & GPB7_ON & GPB8_ON);
-
wait(1000);
-
GPBDAT = (GPB5_OFF | GPB6_OFF | GPB7_OFF | GPB8_OFF);
-
wait(1000);
-
}
-
-
return 0;
-
}
makefile文件
-
objs := head.o init.o setup.o nand.o main.o uart.o iic.o string.o at24c02.o interrupt.o
-
-
iic.bin: $(objs)
-
arm-linux-ld -Tiic.lds -o iic_elf $^ $(shell arm-linux-gcc -msoft-float -print-libgcc-file-name)
-
arm-linux-objcopy -O binary -S iic_elf $@
-
arm-linux-objdump -D -m arm iic_elf > iic.dis
-
-
%.o:%.c
-
arm-linux-gcc -Wall -O2 -c -o $@ $<
-
-
%.o:%.S
-
arm-linux-gcc -Wall -O2 -c -o $@ $<
-
-
clean:
-
rm -f iic.bin iic_elf iic.dis *.o
nand.c文件
-
#include "nand.h"
-
#include "def.h"
-
#include "uart.h"
-
-
/*
-
TQ2440开发板使用的nandflash芯片是 : K9F2G08U0A
-
*/
-
-
/*
-
TQ2440开发板使用的norflash芯片是EN29LV160AB-70TCP
-
*函数功能:判断是nand启动,还是nor启动
-
*返回值:0:nand启动,1:nor启动
-
*/
-
int isBootFromNorFlash(void)
-
{
-
volatile int *p = (volatile int *)0;
-
int val;
-
-
val = *p; /*先把0地址的值,保存到中间变量val*/
-
*p = 0x12345678; /*试图向0地址赋值*/
-
if(*p == 0x12345678) /*向0地址写数据成功*/
-
{
-
/* 写成功, 是nand启动 */
-
*p = val;
-
return 0;
-
}
-
else /*向0地址写数据失败*/
-
{
-
/* NOR不能像内存一样写 */
-
return 1;
-
}
-
}
-
-
/*
-
*功能:重启
-
*/
-
void nand_reset()
-
{
-
/*每操作NandFlash之前必须先片选*/
-
NF_CE_L();
-
/*发送命令之前要先清除RB*/
-
NF_CLEAR_RB();
-
/*向NandFlash写命令:即发送复位命令*/
-
NF_CMD(CMD_RESET);
-
/*注意:命令执行期间,NandFlash存储器忙,所以一直要等待为1:不忙*/
-
NF_WAIT_RB();
-
/*关闭片选*/
-
NF_CE_H();
-
}
-
-
/*
-
*功能:NANDFLASH初始化
-
*注意:1,本程序使用的是ARM9 2440处理器,需要设置 NFCONF,NFCONT这2个寄存器
-
* 2,NandFlash的芯片是K9F2G08U0A。由芯片手册可知:
-
* 容量:(256M+8M)字节,后面的8M叫OOB空间,坏块管理会用到它。
-
* 8bit
-
* 2K个块,每个块有64个page页
-
* 一个page页 =(2K+64)字节
-
* 所以,一个块 = 64页 = 64*(2K+64)字节 =(128K+2K)字节
-
* 一个Nand器件 = 2K个块 = 2K*64个page页 = 128K页
-
* 一个Nand器件 = 128K页 = 128K*(2K+64)字节 = (256M+8M)字节
-
* 3,片内内存+SDRAM+网卡+寄存器:是由CPU统一编址的,由CPU发出地址。
-
* 但是NandFlash不是由CPU统一编址的,所以NandFlash的0地址和片内内存的0地址,不是一回事。
-
* 4,NandFlash中的OOB不参与编址,读取NandFlash上的2049这个地址,不是读取的0页上的地址,是1页上的。
-
* 5,NandFlash控制器的各个控制引脚的时序,不需要程序员来驱动这些引脚,
-
* 程序员只需要设置相关寄存器之后,控制器会自动发出控制信号。
-
*/
-
void nand_init()
-
{
-
//char ret;
-
//char ch;
-
/*NANDFLASH配置寄存器 : NFCONF
-
[13:12]=01:时序图中CLE,ALE持续值设置,1个HCLK时钟周期,如HCLK是100Mhz,则,1个HCLK时钟周期=10ns。
-
[10:8]=011:时序图中TWRPH0持续值设置,3个HCLK时钟周期
-
[6:4]=100:时序图中TWRPH1持续值设置,0个HCLK时钟周期
-
*/
-
rNFCONF = ((TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4));
-
/*NANDFLASH控制寄存器 : NFCONT
-
其他位基本都是初始值或者不需要设置
-
[1]=1:禁止片选,CE引脚输出为1
-
[0]=1:MODE位,使能NandFlash控制器,命令使能CLE引脚设置为1
-
*/
-
//rNFCONT = (1<<4)|(1<<1)|(1<<0);
-
rNFCONT =(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);
-
//非锁定,屏蔽nandflash中断,初始化ECC及锁定main区和spare区(OOB区)ECC,使能nandflash片选及控制器
-
-
nand_reset();
-
-
/* 下面串口打印函数不能用了,因为连接脚本将nand.o放在4k内,uart.o在4k外,不能调用了
-
uart_sendByte('\n');
-
uart_sendByte('w');
-
uart_sendByte('x');
-
uart_sendByte('c');
-
uart_sendByte('\n');
-
-
ret = nand_readId();
-
uart_sendByte('\n');
-
uart_sendString("nand_readId = ");
-
uart_sendByte_hex(ret);
-
uart_sendByte('\n');
-
-
uart_sendString("please input:\n");
-
ch = uart_getch();
-
uart_sendString("your input is:");
-
uart_sendByte(ch);
-
uart_sendByte('\n');
-
*/
-
-
return ;
-
}
-
-
-
/*功能:读取芯片的ID号
-
*注意:查看NandFlash的相关配置是否正确,可以去检查芯片的ID号。如果ID读取正确了,则说明配置正确了。
-
*第1个周期:厂商编号 : 0xEC
-
*第2个周期:设备编号:相同型号的芯片,有相同的ID号 : 0xDA,这个必须正确
-
*第3个周期:内部芯片编号 : 0x10
-
*第4个周期:页的大小 : 0x95
-
*第5个周期:一个page另外带的融错空间的大小 : 0x44
-
*/
-
unsigned char nand_readId()
-
{
-
int i;
-
unsigned char cyc1;
-
unsigned char cyc2;
-
unsigned char cyc3;
-
unsigned char cyc4;
-
unsigned char cyc5;
-
//片选
-
NF_CE_L();
-
//清除RB
-
NF_CLEAR_RB();
-
//发送读ID命令
-
NF_CMD(CMD_READID);
-
//发送地址值:需要读取这个地址上的值,读ID号,规定就是这个地址,看的时序图,只需发送一个地址.
-
NF_ADDR(0x0);
-
for(i=0; i<1000; i++);
-
//从nandflash中读取数据:读5次,看时序图
-
//注意:每次只能读取8位数据,保存在数据寄存器中。
-
cyc1 = NF_RDDATA8();
-
for(i=0; i<100; i++);
-
cyc2 = NF_RDDATA8();
-
for(i=0; i<100; i++);
-
cyc3 = NF_RDDATA8();
-
for(i=0; i<100; i++);
-
cyc4 = NF_RDDATA8();
-
for(i=0; i<100; i++);
-
cyc5 = NF_RDDATA8();
-
for(i=0; i<100; i++);
-
-
//等待命令执行完毕
-
NF_WAIT_RB();
-
//关闭片选
-
NF_CE_H();
-
-
return cyc2;
-
}
-
-
-
/*功能 : 发出地址
-
*注意: 这个地址,不是页编号,是具体的读取NandFlash的起始地址,包括页内地址
-
*/
-
void write_addr(unsigned long addr)
-
{
-
//写地址(要读取数据的起始地址) :
-
//包括 : 列(页内)地址,是低位地址,共A0~A11,在具体的page页内寻址。一个page页=(2K+64)字节
-
// 行(页)地址,是高位地址,共A12~A28,可以寻址到page页。一个Nand器件=2K*64个page页 = 128K页
-
// 高位地址中,A12~A17,可以寻址到block块下面的page页。一个block块=64个page页
-
// 高位地址中,A18~A28,可以寻址到block块。一个Nand器件=2K个block块
-
//要写全部的5个周期的地址
-
-
//32位的page_number : 低29位有效 :
-
//| 28| 27| 26| 25| 24| 23| 22| 21| 20| 19| 18| 17| 16| 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
-
// 对应最后3个地址周期的页内地址的高11位 :
-
//|A28|A27|A26|A25|A24|A23|A22|A21|A20|A19|A18|A17|A16|A15|A14|A13|A12|A11|A10|A09|A08|A07|A06|A05|A04|A03|A02|A01|A00|
-
//29位在5个地址周期中应是如下的分布 :
-
//| I7 | I6 | I5 | I4 | I3 | I2 | I1 | I0 |
-
//| L | L | L | L | L | L | L | A28 | //L表示低电平,即为0
-
//| A27 | A26 | A25 | A24 | A23 | A22 | A21 | A20 |
-
//| A19 | A18 | A17 | A16 | A15 | A14 | A13 | A12 |
-
//| L | L | L | L | A11 | A10 | A9 | A8 |
-
//| A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
-
-
unsigned long i;
-
unsigned long col, page;
-
-
col = addr & (2048 -1); //截取低位 : 取2048的原因:本程序使用的nand芯片的一个page页是2048字节
-
page = addr / 2048; //截取高位 : 第page页,从第0页开始
-
-
//因为第11位已经丢弃了,所以A11肯定是0,所以这里其实只能寻址2047以内的页的main区,
-
//不可能寻址到2047以后的页内spare区,超过2047的地址,已经表示下一页的地址了。
-
NF_ADDR(col & 0xFF); //列(页内)地址 A0~A7
-
for(i=0; i<100; i++);
-
NF_ADDR((col >> 8) & 0x0F); //列(页内)地址 A8~A11
-
for(i=0; i<100; i++);
-
-
NF_ADDR(page & 0xFF); //行(页)地址 A12~A19
-
for(i=0; i<100; i++);
-
NF_ADDR((page >> 8) & 0xFF); //行(页)地址 A20~A27
-
for(i=0; i<100; i++);
-
NF_ADDR((page >> 16) & 0x01); //行(页)地址 A28
-
for(i=0; i<100; i++);
-
}
-
-
/*
-
*功能 : 从NandFlash位置start_addr开始读取数据,将数据复制到指定的SDRAM地址buf处,共复制size个字节。
-
* 数据方向:(Nand的start_addr->SDRAM的buf)
-
*参数 : buf : SDRAM的缓冲区起始地址:0x30001000
-
* start_addr : 如果要确保正确使用本函数来传输数据,这里的地址:不是页编号,但是必须每一个page页的开始地址,即页边界地址,即地址要2K对齐。
-
* size : 复制的字节数,最好是页空间整数倍。即使不指定为页的整数倍,也会读取2K整数倍的空间
-
*注意 : 1,read是相对于cpu说的,cpu读取nand中的程序代码,拷贝到SDRAM中去执行。
-
* 2,如果在读取数据的过程中不进行ECC校验判断,则读操作比较简单,
-
* 在发出读命令的两个周期之间,发出要读取的页地址,然后读取数据即可
-
* 3,如果nand起始地址不是页对齐,则使用下面的nand_read2函数。
-
* 使用本函数,nand起始地址一定要页对齐
-
* 4,本函数的读操作是以页为单位进行的,size即使不是页的整数倍,也会读取整数倍的页数据。
-
* 5,如果这里的起始地址,非页对齐的地址,可能会导致我的程序重启(应该是读指令异常)
-
*/
-
void nand_read(unsigned char *buf, unsigned long start_addr, int size)
-
{
-
unsigned long i = 0, j = 0;
-
//1. 选中,片选
-
NF_CE_L();
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
//清除RB
-
NF_CLEAR_RB();
-
// 2. 发出读命令00h,读NandFlash时的第1个命令
-
//0x00命令发出后,即相当于向NFCMD寄存器中写入了0,nandflash控制器会自动发出各种控制信号,
-
//会自动满足时序图的要求,不再需要人工参与了。
-
//这样,nandflash控制器提供的几个寄存器,就简化了对nandflash的操作。
-
NF_CMD(CMD_READ1);
-
//3. 发出地址,写地址 (分5步发出)
-
write_addr(i);
-
//4. 发出读命令30h,读NandFlash时的第2个命令
-
NF_CMD(CMD_READ2);
-
//注意 : 因为这里没有继续发出页内随意读命令,所以当i=2048,2049的时候,
-
//上面发地址的函数write_addr(i);经过处理,已经跳过第0页了,是要读取第1页的第1,2个字节。
-
-
//5. 判断状态,等待NandFlash不忙
-
NF_WAIT_RB();
-
-
//6. 读数据 : 虽然读写过程可以不从页边界开始,但在这里必须从页边界开始读写至页结束
-
//注意 : uboot的命令 : nand dump 0 //从nand的0地址开始,1次读取1页的数据。
-
for(j=0; j<2048; j++, i++)
-
{
-
//1次从nandflash的8位IO口上读取8位数据。
-
//注意:每次读此寄存器,即执行一次下面语句,就会启动对nandflash的1次读操作。
-
//前一次读操作未完成,是不会启动下一次读操作的。
-
*buf = NF_RDDATA8();
-
buf++;
-
}
-
uart_sendString("#"); //2个nand read函数:目的是:main之后才初始化uart,可以使用这个带打印#的函数
-
}
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
}
-
-
-
/*
-
*功能 : 从NandFlash位置start_addr开始读取数据,将数据复制到指定的SDRAM地址buf处,共复制size个字节。
-
* 数据方向:(Nand的start_addr->SDRAM的buf)
-
*参数 : buf : SDRAM的缓冲区起始地址:0x30001000
-
* start_addr : 如果要确保正确使用本函数来传输数据,这里的地址:不是页编号,
-
* 但是必须每一个page页的开始地址,即页边界地址,即地址要2K对齐。
-
* size : 复制的字节数,最好是页空间整数倍。即使不指定为页的整数倍,也会读取2K整数倍的空间
-
*注意 : 1,read是相对于cpu说的,cpu读取nand中的程序代码,拷贝到SDRAM中去执行。
-
* 2,如果在读取数据的过程中不进行ECC校验判断,则读操作比较简单,
-
* 在发出读命令的两个周期之间,发出要读取的页地址,然后读取数据即可
-
* 3,如果nand起始地址不是页对齐,则使用下面的nand_read2函数。
-
* 使用本函数,nand起始地址一定要页对齐
-
* 4,本函数的读操作是以页为单位进行的,size即使不是页的整数倍,也会读取整数倍的页数据。
-
* 5,如果这里的起始地址,非页对齐的地址,可能会导致我的程序重启(应该是读指令异常)
-
*/
-
void nand_read_1(unsigned char *buf, unsigned long start_addr, int size)
-
{
-
unsigned long i = 0, j = 0;
-
//1. 选中,片选
-
NF_CE_L();
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
//清除RB
-
NF_CLEAR_RB();
-
// 2. 发出读命令00h,读NandFlash时的第1个命令
-
//0x00命令发出后,即相当于向NFCMD寄存器中写入了0,nandflash控制器会自动发出各种控制信号,
-
//会自动满足时序图的要求,不再需要人工参与了。
-
//这样,nandflash控制器提供的几个寄存器,就简化了对nandflash的操作。
-
NF_CMD(CMD_READ1);
-
//3. 发出地址,写地址 (分5步发出)
-
write_addr(i);
-
//4. 发出读命令30h,读NandFlash时的第2个命令
-
NF_CMD(CMD_READ2);
-
//注意 : 因为这里没有继续发出页内随意读命令,所以当i=2048,2049的时候,
-
//上面发地址的函数write_addr(i);经过处理,已经跳过第0页了,是要读取第1页的第1,2个字节。
-
-
//5. 判断状态,等待NandFlash不忙
-
NF_WAIT_RB();
-
-
//6. 读数据 : 虽然读写过程可以不从页边界开始,但在这里必须从页边界开始读写至页结束
-
//注意 : uboot的命令 : nand dump 0 //从nand的0地址开始,1次读取1页的数据。
-
for(j=0; j<2048; j++, i++)
-
{
-
//1次从nandflash的8位IO口上读取8位数据。
-
//注意:每次读此寄存器,即执行一次下面语句,就会启动对nandflash的1次读操作。
-
//前一次读操作未完成,是不会启动下一次读操作的。
-
*buf = NF_RDDATA8();
-
buf++;
-
}
-
}
-
//关闭片选
-
NF_CE_H();
-
-
}
-
-
-
/*
-
* buf : 目标地址 = 0x30000000,这是SDRAM的起始地址
-
* start_addr : 源地址 = 4096,运行地址在SDRAM中的代码保存在NAND Flash 4096地址开始处
-
* size : 复制长度 = 600K,对于本实验,这是足够了,没有图片的话,16K足够了,有图片了,得300K
-
*/
-
int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size)
-
{
-
unsigned long i = 0;
-
-
//norflash启动开发板
-
if(isBootFromNorFlash())
-
{
-
while (i < size)
-
{
-
buf[i] = *((char *)start_addr);
-
i++;
-
start_addr++;
-
}
-
}
-
else //nandflash启动开发板
-
{
-
nand_read_1(buf, start_addr, size);
-
}
-
-
return 0;
-
}
-
-
-
/*
-
*功能 : 从NandFlash位置start_addr开始读取数据,将数据复制到指定的SDRAM地址buf处,共复制size个字节。
-
* 数据方向:(Nand的start_addr->SDRAM的buf)
-
*参数 : buf : SDRAM的缓冲区起始地址:0x30001000
-
* start_addr : 不是页编号,是具体的页内地址,可以不是每一个page页的开始地址,
-
* 即可以是非页边界地址,即地址没有2K对齐的要求。当然地址是页对齐也会支持
-
* size : 复制的字节数,可以是任意字节数。
-
* 这个大小仅仅只包括页内main区的空间,不包括页内spare区的空间。
-
*注意 : 1,read是相对于cpu说的,cpu读取nand中的程序代码,拷贝到SDRAM中去执行。
-
* 2,如果在读取数据的过程中不进行ECC校验判断,则读操作比较简单,
-
* 在发出读命令的两个周期之间,发出要读取的页地址,然后读取数据即可
-
* 3,如果nand起始地址不是页对齐,则使用下面的nand_read2函数。
-
* 当然,页对齐的地址,也可以使用本函数。
-
* 4,本函数已经测试通过,可以使用,功能很强大,完全兼容上面的nand_read函数。
-
*/
-
unsigned int nand_read2(unsigned char * buf, unsigned long start_addr, int size)
-
{
-
unsigned long i = 0, j = 0;
-
unsigned long page_num = 0; //读取的页page的数量
-
unsigned long page_yu_num = 0; //不足一页page的字节数
-
unsigned long start_yu_num = 0; //start_addr起始地址 到 本页末尾地址 的个数。
-
-
page_num = size/2048; //+1=读取的页page的数量
-
page_yu_num = size & (2048 -1); //不足一页page的字节数
-
start_yu_num = 2048-(start_addr & (2048 -1)); //这个非对齐的页的起始地址 到 本页末尾地址 的个数。
-
-
//1. 选中,片选
-
NF_CE_L();
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
//清除RB
-
NF_CLEAR_RB();
-
// 2. 发出读命令00h,读NandFlash时的第1个命令
-
NF_CMD(CMD_READ1);
-
//3. 发出地址,写地址 (分5步发出)
-
write_addr(i);
-
//4. 发出读命令30h,读NandFlash时的第2个命令
-
NF_CMD(CMD_READ2);
-
//5. 判断状态,等待NandFlash不忙
-
NF_WAIT_RB();
-
//6. 读数据
-
if((i & (2048 -1)) == 0) //起始地址:是页对齐的地址
-
{
-
if(page_num > 0) //剩下需要读取的字节数>1页page
-
{
-
for(j=0; j<2048; j++, i++) //读取一页
-
{
-
*buf = NF_RDDATA8();
-
buf++;
-
}
-
page_num--; //每读一页,则少一页
-
}
-
else //剩下需要读取的字节数<1页page
-
{
-
for(j=0; j<page_yu_num; j++, i++) //读取不足一页的数据
-
{
-
*buf = NF_RDDATA8();
-
buf++;
-
}
-
}
-
}
-
else //起始地址:非页对齐的地址
-
{
-
//start_yu_num:是 这个非对齐的页的起始地址 到 本页末尾地址 的个数。
-
for(j=0; j<start_yu_num; j++, i++) //小于2048也行
-
{
-
*buf = NF_RDDATA8();
-
buf++;
-
}
-
}
-
}
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
-
return 0;
-
}
-
-
-
/*
-
*函数功能 : 页内地址随意读,可以读取页内2047以内的main区地址,也可以读取页内2047以外的64字节的spare区的地址。
-
*函数参数 : page_number : 是页编号,是第几个页。从第9页开始。
-
* addr : 页内地址,这个地址的范围一定是: <=2K+后面的64字节的
-
*注意 : 1, 页读和页写,一般是从页的首地址开始读、写,
-
* 随意读、写实现了在一页范围内任意地址的读、写。
-
* 2, 随意读操作是在页读操作后输入随意读命令和页内列地址,
-
* 这样就可以读取到列地址所指定地址的数据。
-
*页读/写,页内地址随意读/写的区别:
-
* 页读/写 : 虽然读写过程可以不从页边界开始,但在正式场合下还是建议从页边界开始读写至页结束
-
* 页内地址随意读/写 : 可以读取页内2047以内的main区地址,也可以读取页内2047以外的64字节的spare区的地址。
-
*/
-
unsigned char nand_read_random(unsigned long page_number, unsigned long addr)
-
{
-
unsigned char ch;
-
//1,打开Nand Flash片选
-
NF_CE_L();
-
//清RnB信号
-
NF_CLEAR_RB();
-
//2,页读命令周期1
-
NF_CMD(CMD_READ1);
-
//3,写入5个地址周期
-
NF_ADDR(0x00); //列(页内)地址A0~A7
-
NF_ADDR(0x00); //列(页内)地址A8~A11
-
NF_ADDR(page_number & 0xff); //行(页)地址A12~A19
-
NF_ADDR((page_number >> 8) & 0xff); //行(页)地址A20~A27
-
NF_ADDR((page_number >> 16) & 0x01); //行(页)地址A28 (低1位)
-
//4,页读命令周期2
-
NF_CMD(CMD_READ2);
-
//等待RnB信号变高,即不忙
-
NF_WAIT_RB();
-
//5,随意读命令周期1
-
NF_CMD(CMD_RANDOMREAD1);
-
//6,写页内地址,共12根线,可寻址2^12=2K*2空间,每一页的main区是2K空间,spare区只有64字节
-
//如果读取地址是2048+6,没有像write_addr()函数一样,来截取低11位,则A11肯定是1,
-
//后面的+6,就可以寻址到页内spare区的第6个字节。
-
NF_ADDR((char)(addr & 0xff)); //列(页内)地址A0~A7
-
NF_ADDR((char)(addr >> 8) & 0x0f); //列(页内)地址A8~A11 (低4位)
-
//7,随意读命令周期2
-
NF_CMD(CMD_RANDOMREAD2);
-
//8, 读取指定地址的数据
-
ch = NF_RDDATA8();
-
//9, 关闭Nand Flash片选
-
NF_CE_H();
-
-
return ch; //将读取的数据返回
-
}
-
-
-
/*
-
*功能 : 识别坏块的读取: 从NandFlash位置start_addr开始读取数据,将数据复制到指定的SDRAM地址buf处,共复制size个字节。
-
* 数据方向:(Nand的start_addr->SDRAM的buf)
-
*参数 : buf : SDRAM的缓冲区起始地址:0x30001000
-
* start_addr : 不是页编号,是 页(2K)对齐的地址。
-
* size : 复制的字节数,是页空间整数倍
-
*注意 : 1,read是相对于cpu说的,cpu读取nand中的程序代码,拷贝到SDRAM中去执行。
-
* 2,读操作是以页为单位进行的
-
* 3,为了更准确地读取数据,则在读取完数据之后还要进行ECC校验判断,以确定所读取的数据是否正确。
-
* 4,为了配合ECC校验,程序的输入参数start_addr最好直接就为K9F2G08U0A的第几页
-
* 例如我们要读取第128064页中的内容,可以调用该程序为 : start_addr=128064*2048
-
* 由于第128064页是第2001块中的第0页 start_addr=(128064=2001×64+0)*2048
-
* 即 start_addr 最好是 页(2K)对齐的地址
-
*/
-
unsigned int nand_read_oob(unsigned char * buf, unsigned long start_addr, int size)
-
{
-
unsigned long ret = 0;
-
unsigned long col, page;
-
unsigned long i = 0;
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
col = start_addr & (2048 - 1); //截取低位 : 如果start_addr是页对齐地址,则col肯定是0
-
page = start_addr / 2048; //截取高位 : 是第几个页
-
-
ret = nand_read_oob_page(buf, page);
-
if(ret == 0) //读取成功
-
{
-
i = i + 2048; //继续写
-
buf = buf + 2048;
-
}
-
else if(ret == 1) //读取失败
-
{
-
//这里应该打印报错语句
-
return 1;
-
}
-
}
-
return 0;
-
}
-
-
-
/*
-
*功能 : 识别坏块的读取: 从NandFlash位置第page_number页开始读取数据,将数据复制到buf处,
-
* 一次共复制一页(2K)个字节的数据,且只复制一页的数据。
-
* 数据方向:(Nand的start_addr--->SDRAM的buf)
-
*参数 : buf : SDRAM的缓冲区起始地址:0x30001000
-
* page_number : 是页编号,是第几个页。
-
*注意 : 1,read是相对于cpu说的,cpu读取nand中的程序代码,拷贝到buf。
-
* 2,读操作是以页为单位进行的,并且一次只读一页数据。
-
* 3,为了更准确地读取数据,则在读取完数据之后还要进行ECC校验判断,以确定所读取的数据是否正确。
-
* 4,这段程序是把某一页的内容读取到数组buffer中。该程序的输入参数直接就为K9F2G0
-
* 8U0A的第几页,例如我们要读取第128064页中的内容,可以调用该程序为:nand_read_oob_page(128064)。
-
* 由于第128064页是第2001块中的第0页(128064=2001×64+0),所以为了更清楚地表示页
-
* 与块之间的关系,也可以写为:nand_read_oob_page(2001*64)。
-
*/
-
unsigned int nand_read_oob_page(unsigned char * buf, unsigned long page_number)
-
{
-
unsigned long i;
-
unsigned long meccd; //保存从OOB区读取出来的值,共4个字节:第2048+2049+2050+2051字节
-
unsigned long seccd; //保存从OOB区读取出来的值,共2个字节:第2052+2053字节
-
-
//复位ECC
-
NF_RSTECC();
-
//解锁main区ECC : 以便main区产生ECC码
-
NF_MECC_UnLock();
-
//1. 选中,片选
-
NF_CE_L();
-
//清除RB
-
NF_CLEAR_RB();
-
// 2. 发出读命令00h,读NandFlash时的第1个命令
-
NF_CMD(CMD_READ1);
-
//3. 发出地址,写地址 (分5步发出)
-
NF_ADDR(0x00);
-
NF_ADDR(0x00);
-
NF_ADDR(page_number & 0xff);
-
NF_ADDR((page_number >>8) & 0xff);
-
NF_ADDR((page_number >>16) & 0xff);
-
//4. 发出读命令30h,读NandFlash时的第2个命令
-
NF_CMD(CMD_READ2);
-
//5. 判断状态,等待NandFlash不忙
-
NF_WAIT_RB();
-
//6. 读数据
-
for(i=0; i<2048; i++)
-
{
-
//1次从nandflash的8位IO口上读取8位数据。
-
buf[i] = NF_RDDATA8();
-
}
-
//注意 : 读取完数据之后,硬件将自动产生main区ECC码
-
//锁定main区ECC值 : 自动产生的main区ECC码,不是保存到寄存器(NFMECCD0,1)中,是保存在另外一个位置。
-
//NFMECCD0,1用来存放已有的ECC码,已有的ECC码保存在OOB区,即spare区。
-
NF_MECC_Lock();
-
-
//解锁spare区ECC : 以便spare区产生ECC码
-
NF_SECC_UnLock();
-
//读spare区的前32位(4个字节)地址内容,即第2048~2051地址,这4个字节为写数据时,产生的main区的ECC码。
-
meccd = NF_RDDATA();
-
//meccd的顺序 : ECC0(0~7位)+ECC1(8~15位)+ECC2(16~23位)+ECC3(24~31位)
-
//注意 : 把读取到的main区的ECC校验码放入 NFMECCD0 和 NEMECCD1 的相应位置内。
-
//具体放置位置 : ECC0:NFMECCD0的0~7位 ECC1:NFMECCD0的16~23位。
-
// ECC2:NFMECCD1的0~7位 ECC3:NFMECCD1的16~23位。
-
rNFMECCD0 = ((meccd & 0xff00) << 8) | (meccd & 0xff);
-
rNFMECCD1 = ((meccd & 0xff000000) >>8) | ((meccd & 0xff0000)>>16);
-
//锁定spare区的ECC值 : 自动产生的spare区ECC码,不是保存到NFSECCD中,是保存在另外一个位置。
-
//NFSECCD用来存放已有的spare区的ECC码,已有的spare区ECC码保存在OOB区。
-
NF_SECC_Lock();
-
-
//继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值
-
seccd = NF_RDDATA();
-
//把读取到的spare区的ECC校验码放入NFSECCD的相应位置内
-
//seccd的顺序 : spare区ECC0(0~7位)+spare区ECC1(8~15位)
-
//具体放置位置 : spare区ECC0:NFSECCD的0~7位。
-
// spare区ECC1:NFSECCD的16~23位。
-
rNFSECCD = ((seccd & 0xff00) << 8) | (seccd & 0xff);
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
-
//读完一页数据之后,自动算出一个ECC码,这个自动算出来的ECC代码 和
-
//已经存在的ECC码(由OOB区保存到NFMECCD0+NFMECCD1+NFMECCD中),自动进行比较,
-
//比较的结果,保存在NFESTAT寄存器中,[1:0]=00:表示main区无错误 [3:2]=00:表示spare区无错误。
-
if((rNFESTAT0 & 0xf) == 0)
-
{
-
return 0; //读取的数据都正确
-
}
-
else
-
{
-
return 1; //读取完成,但是数据错误
-
}
-
}
-
-
-
/*
-
*功能 : 擦除指定的块
-
*参数 : block_number : 块号
-
*注意 : 1,一个NandFlash块有2K个块。块大小为128K+2K字节。
-
* 2,块被擦除后nand对应的块中的数据全部是1
-
* 3,命令字分为2个阶段
-
* 4,擦除是以块为单位进行的
-
*/
-
unsigned char nand_erase(unsigned long block_number)
-
{
-
unsigned long i;
-
unsigned char stat;
-
//1,选中,片选
-
NF_CE_L();
-
//清除RB
-
NF_CLEAR_RB();
-
//2,发出擦除命令0x60,写入第1个擦除命令
-
NF_CMD(CMD_ERASE1);
-
//写块的地址 : 注意 : 只写块的地址就行,块地址都在行(页)地址的高位上,
-
//所以前面2个周期的列(页内)地址写0就行,后面3个周期的页地址的高11位要具体写出。
-
//列地址 : 就是页内地址, 共12位
-
//行地址 : 就是页地址, 共17位 : 高11位(A28-A18)是2K的块地址,低6位是64的页地址。
-
-
//32位block_number:低11位有效 :
-
//| 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
-
// 对应最后3个地址周期的页内地址的高11位 :
-
//| A28 | A27 | A26 | A25 | A24 | A23 | A22 | A21 | A20 | A19 | A18 |
-
//高11位在后面3个地址周期中应是如下的分布 :
-
//| I7 | I6 | I5 | I4 | I3 | I2 | I1 | I0 |
-
//| | | | | | | | A28 |
-
//| A27 | A26 | A25 | A24 | A23 | A22 | A21 | A20 |
-
//| A19 | A18 | | | | | | |
-
//3,仅需发出块地址(分3步发出)
-
NF_ADDR((block_number << 6) & 0xFF);
-
NF_ADDR((block_number >> 2) & 0xFF);
-
NF_ADDR((block_number >> 10) & 0xFF);
-
//4, 发出擦除命令0xD0, 写入第2个擦除命令
-
NF_CMD(CMD_ERASE2);
-
-
for(i=0; i<1000; i++);
-
//5, 写入读状态寄存器的命令
-
NF_CMD(CMD_STATUS);
-
//6, 判断状态
-
do
-
{
-
//读取8位状态数据
-
stat = NF_RDDATA8();
-
}
-
while(!(stat & 0x40)); //等到第6位为1 : 表示读取数据完成
-
//7, 取消选中,关闭片选
-
NF_CE_H();
-
-
//约定用0x66来表示擦除成功,或者写成功
-
return 0;
-
}
-
-
-
/*
-
*函数功能 : 识别坏块的擦除
-
*函数参数 : 块编号,从第0块开始,2K个块(第0块~第2047块)
-
*函数返回值 :
-
* 1 : 是坏块,不能擦除
-
*注意 : 1,擦除是以块为单位进行的,因此在写地址周期时,只需写三个行周期,并且只写块地址
-
* 并且,擦除的范围是64个页,页内的main区+spare区的64字节都擦除成0xff。
-
* 2,只能擦除好块,不能擦除坏块,擦除坏块的操作会失败。
-
* 也可以根据这个特性来判断一个块是不是坏块:当第1页的spare的第6个字节的值感觉不可靠,
-
* 并且,这个块的数据不重要,可以清除时,可以直接擦除这个块,如果擦除成功,表示是好块,
-
* 如果擦除失败,则表示这个块是坏块,我们再在spare区的第6个字节标记一下,这是个坏块。
-
* 3,该程序的输入参数为K9F2G08U0A的第几块,例如我们要擦除第2001块,
-
* 则调用该函数为 : nand_erase_oob(2001)。
-
-
//写地址(要读取数据的起始地址) :
-
//包括 : 列(页内)地址,是低位地址,共A0~A11,在具体的page页内寻址。2^12=2K*2 一个page页=(2K+64)字节
-
// 行(页)地址,是高位地址,共A12~A28,可以寻址到page页。2^17=128K, 一个Nand器件=2K*64个page页 = 128K页
-
// 高位地址中,A12~A17,可以寻址到block块下面的page页。2^6=64, 一个block块=64个page页
-
// 高位地址中,A18~A28,可以寻址到block块。2^11=2K, 一个Nand器件=2K个block块
-
//要写全部的5个周期的地址
-
-
//32位的page_number : 低29位有效 :
-
//| 28| 27| 26| 25| 24| 23| 22| 21| 20| 19| 18| 17| 16| 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
-
// 对应最后3个地址周期的页内地址的高11位 :
-
//|A28|A27|A26|A25|A24|A23|A22|A21|A20|A19|A18|A17|A16|A15|A14|A13|A12|A11|A10|A09|A08|A07|A06|A05|A04|A03|A02|A01|A00|
-
//29位在5个地址周期中应是如下的分布 :
-
//| I7 | I6 | I5 | I4 | I3 | I2 | I1 | I0 |
-
//| L | L | L | L | L | L | L | A28 |
-
//| A27 | A26 | A25 | A24 | A23 | A22 | A21 | A20 |
-
//| A19 | A18 | A17 | A16 | A15 | A14 | A13 | A12 |
-
//| L | L | L | L | A11 | A10 | A9 | A8 |
-
//| A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
-
*/
-
unsigned int nand_erase_oob(unsigned long block_number)
-
{
-
unsigned long i = 0;
-
unsigned char stat; //保存 状态寄存器 中的值
-
unsigned int flag; //坏块标志
-
//判断该块是否为坏块
-
flag = nand_IsBadBlock(block_number);
-
if(flag == 1) //是坏块
-
{
-
return 1; //是坏块,返回1
-
}
-
//本块不是坏块,才继续走到下面来
-
//1,选中,片选
-
NF_CE_L();
-
//清除RB
-
NF_CLEAR_RB();
-
//2,发出擦除命令0x60,写入第1个擦除命令
-
NF_CMD(CMD_ERASE1);
-
//3,仅需发出块地址(分3步发出)
-
//行(页)地址,是高位地址,共A12~A28,可以寻址到page页。
-
//A12~A17,可以寻址到block块下面的page页; A18~A28,可以寻址到block块。
-
NF_ADDR((block_number << 6) & 0xc0); //行(页)地址 A18~A19(高2位)
-
NF_ADDR((block_number >> 2) & 0xff); //行(页)地址 A20~A27(8位)
-
NF_ADDR((block_number >> 10) & 0x01); //行(页)地址 A28 (低1位)
-
//4, 发出擦除命令0xD0,写入第2个擦除命令
-
NF_CMD(CMD_ERASE2);
-
//会擦除本块的所有64个页,包括页内的main区空间+页内的spare区空间
-
for(i=0; i<100; i++); //延时一段时间
-
//5, 读状态命令
-
NF_CMD(CMD_STATUS);
-
//6, 判断状态
-
do
-
{
-
//将状态寄存器的值 赋值给stat。只取8位状态数据。
-
stat=NF_RDDATA();
-
}
-
while(!(stat & 0x40)); //等到第6位为1 : 表示读取状态寄存器完成。
-
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
-
//7, 取消选中,关闭片选
-
NF_CE_H();
-
//判断状态寄存器的值的第0位是否为0,为0则表示擦除操作成功完成,否则表示擦除操作失败
-
if(stat & 0x01)
-
{
-
//如果擦除失败,则标记该块为坏块,也即恢复刚才可能擦除的坏块标志。
-
flag = nand_MarkBadBlock(block_number); //标注该块为坏块
-
if(flag == 0)
-
{
-
return 2; //标注坏块成功,擦除操作失败
-
}
-
else
-
{
-
return 3; //标注坏块失败,擦除操作失败
-
}
-
}
-
else
-
{
-
return 0; //擦除操作成功
-
}
-
}
-
-
-
/*功能 : 将buf处开始取数据,写入NandFlash的start_addr地址处,共写入size个字节。
-
* 数据方向:(SDRAM的buf->Nand的start_addr)
-
*参数 : buf : SDRAM的缓冲区起始地址
-
* start_addr : 如果要确保正确使用本函数来传输数据,这里的地址:不是页编号,
-
* 但是必须每一个page页的开始地址,即页边界地址,即地址要2K对齐。
-
* size : 复制的字节数,最好是页空间整数倍。即使不指定为页的整数倍,也会读取2K整数倍的空间
-
*注意 : 1,write是相对于cpu说的,cpu写数据到nand中,SDRAM中的数据拷贝到Nand中。
-
* 2,页写操作的大致流程为:在两个写命令周期之间分别写入页地址和数据
-
* 3,如果为了保证下次读取该数据时的正确性,还需要把main区的ECC值和spare区
-
* 的ECC值写入到该页的spare区内。
-
*/
-
unsigned int nand_write(unsigned char * buf, unsigned long start_addr, int size)
-
{
-
unsigned long i,j;
-
unsigned char stat;
-
//1,选中,片选
-
NF_CE_L();
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
//清除RB
-
NF_CLEAR_RB();
-
//2. 发出写命令0x80,写NandFlash时的第1个命令 : NandFlash写准备
-
NF_CMD(CMD_WRITE1);
-
//3. 发出地址,写地址(分5步发出)
-
write_addr(i);
-
//4, 写数据 : 虽然读写过程可以不从页边界开始,但在这里必须从页边界开始读写至页结束
-
for(j=0; j<2048; j++, i++)
-
{
-
//1次写8位的数据给NandFlash的8位IO口
-
//注意:每次写这个寄存器,即执行一次下面的语句,都会启动一次nandflash的写数据操作,
-
//前一次写数据操作不完成,是不会启动下一次写数据操作的
-
NF_WRDATA8(*buf);
-
buf++;
-
}
-
-
//5. 发出写命令0x10,写NandFlash时的第2个命令 : 启动写操作
-
//此时,Flash内部会自动完成写,校验操作。
-
NF_CMD(CMD_WRITE2);
-
for(j=0; j<100; j++);
-
//6. 判断状态,写入读状态寄存器的命令 : 用来判断当前写操作是否完成,是否成功
-
NF_CMD(CMD_STATUS);
-
do
-
{
-
//读取8位状态数据
-
stat = NF_RDDATA8();
-
}
-
while(!(stat & 0x40)); //等到第6位为1 : 表示写数据完成。
-
}
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
-
return 0;
-
}
-
-
-
/*函数功能 : 将buf处开始取数据,写入NandFlash的start_addr地址处,共写入size个字节。
-
* 数据方向:(SDRAM的buf->Nand的start_addr)
-
*函数参数 : buf : SDRAM的缓冲区起始地址
-
* start_addr : 不是页编号,是具体的页内地址,可以不是每一个page页的开始地址,
-
* 即可以是非页边界地址,即地址没有2K对齐的要求。当然地址是页对齐也会支持
-
* size : 复制的字节数,可以是任意字节数。
-
* 这个大小仅仅只包括页内main区的空间,不包括页内spare区的空间。*注意 : 1,write是相对于cpu说的,cpu写数据到nand中,SDRAM中的数据拷贝到Nand中。
-
*注意 : 1,页写操作的大致流程为:在两个写命令周期之间分别写入页地址和数据
-
* 2,如果为了保证下次读取该数据时的正确性,还需要把main区的ECC值和spare区
-
* 的ECC值写入到该页的spare区内。
-
* 3,如果nand起始地址不是页对齐,则使用下面的 nand_write2 函数。
-
* 当然,页对齐的地址,也可以使用本函数。
-
* 4,本函数已经测试通过,可以使用,功能很强大,完全兼容上面的 nand_write 函数。
-
*/
-
unsigned int nand_write2(unsigned char * buf, unsigned long start_addr, int size)
-
{
-
unsigned long i,j;
-
unsigned char stat;
-
-
unsigned long page_num = 0; //写入的页page的数量
-
unsigned long page_yu_num = 0; //不足一页page的字节数
-
unsigned long start_yu_num = 0; //start_addr起始地址 到 本页末尾地址 的个数。
-
-
page_num = size/2048; //写入的页page的数量
-
page_yu_num = size & (2048 -1); //不足一页page的字节数
-
start_yu_num = 2048-(start_addr & (2048 -1)); //这个非对齐的页的起始地址 到 本页末尾地址 的个数。
-
-
//1,选中,片选
-
NF_CE_L();
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
//清除RB
-
NF_CLEAR_RB();
-
//2. 发出写命令0x80,写NandFlash时的第1个命令 : NandFlash写准备
-
NF_CMD(CMD_WRITE1);
-
//3. 发出地址,写地址(分5步发出)
-
write_addr(i);
-
//4, 写数据 : 虽然读写过程可以不从页边界开始,但在这里必须从页边界开始读写至页结束
-
if((i & (2048 -1)) == 0) //起始地址:是页对齐的地址
-
{
-
if(page_num > 0) //剩下需要写入的字节数>1页page
-
{
-
for(j=0; j<2048; j++, i++)
-
{
-
//1次写8位的数据给NandFlash的8位IO口
-
//注意:每次写这个寄存器,即执行一次下面的语句,都会启动一次nandflash的写数据操作,
-
//前一次写数据操作不完成,是不会启动下一次写数据操作的
-
NF_WRDATA8(*buf);
-
buf++;
-
}
-
}
-
else //剩下需要写入的字节数<1页page
-
{
-
for(j=0; j<page_yu_num; j++, i++) //写入不足一页的数据
-
{
-
NF_WRDATA8(*buf);
-
buf++;
-
}
-
}
-
}
-
else //起始地址:非页对齐的地址
-
{
-
//start_yu_num:是 这个非对齐的页的起始地址 到 本页末尾地址 的个数。
-
for(j=0; j<start_yu_num; j++, i++) //小于2048也行
-
{
-
NF_WRDATA8(*buf);
-
buf++;
-
}
-
}
-
-
//5. 发出写命令0x10,写NandFlash时的第2个命令 : 启动写操作
-
//此时,Flash内部会自动完成写,校验操作。
-
NF_CMD(CMD_WRITE2);
-
for(j=0; j<100; j++);
-
//6. 判断状态,写入读状态寄存器的命令 : 用来判断当前写操作是否完成,是否成功
-
NF_CMD(CMD_STATUS);
-
do
-
{
-
//读取8位状态数据
-
stat = NF_RDDATA8();
-
}
-
while(!(stat & 0x40)); //等到第6位为1 : 表示写数据完成。
-
}
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
-
return 0;
-
}
-
-
-
/*
-
*函数功能 : 页内地址随意写
-
*函数参数 : page_number : 是页编号,从第0页开始。
-
* 1块=64页,第2块最开始的一页是第64页
-
* addr : 页内地址(包括2K的main区空间+64字节的spare区空间)
-
* data : 要写入的1字节的数据
-
*函数返回值 :
-
* 0 : 写入成功
-
* 1 : 写入失败
-
*注意 : 1, 页读和页写是从页的首地址开始读、写,
-
* 随意读、写实现了在一页范围内任意地址的读、写。
-
* 2, 随意写操作是在页写操作的第二个页写命令周期前,输入随意写命令和页内列地址,
-
* 以及要写入的数据,这样就可以把数据写入到列地址所指定的地址内。
-
*/
-
unsigned int nand_write_random(unsigned long page_number, unsigned long addr, unsigned char data)
-
{
-
unsigned int i = 0;
-
unsigned char stat; //保存状态寄存器的值
-
//1,打开Nand Flash片选
-
NF_CE_L();
-
//清RnB信号
-
NF_CLEAR_RB();
-
//2,页写命令周期1
-
NF_CMD(CMD_WRITE1);
-
//3,写入5个地址周期
-
NF_ADDR(0x00); //列地址A0~A7
-
NF_ADDR(0x00); //列地址A8~A11
-
NF_ADDR(page_number & 0xff); //行地址A12~A19
-
NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27
-
NF_ADDR((page_number >> 16) & 0x01); //行地址A28
-
//4,随意写命令
-
NF_CMD(CMD_RANDOMWRITE);
-
//5,页内地址
-
NF_ADDR((char)(addr & 0xff)); //列地址A0~A7
-
NF_ADDR((char)((addr >> 8) & 0x0f)); //列地址A8~A11
-
//6,写入数据
-
NF_WRDATA8(data);
-
//7,页写命令周期2
-
NF_CMD(CMD_WRITE2);
-
for(i=0; i<100; i++); //延时一段时间
-
//8. 判断状态,写入读状态寄存器的命令 : 用来判断当前写操作是否完成,是否成功
-
NF_CMD(CMD_STATUS);
-
do
-
{
-
stat = NF_RDDATA8();
-
}
-
while(!(stat&0x40)); //等待第6位为1,表示写数据完成
-
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
-
-
//9,关闭Nand Flash片选
-
NF_CE_H();
-
-
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
-
if(stat &0x1)
-
{
-
return 1; //写入失败
-
}
-
else
-
{
-
return 0; //写入成功
-
}
-
}
-
-
-
/*功能 : 识别坏块的写 : 将buf处开始的数据,写入NandFlash的start_addr地址处,共写入size个字节。
-
* 数据方向:(SDRAM的buf->Nand的start_addr)
-
*参数 : buf : SDRAM的缓冲区起始地址
-
* start_addr : 不是页编号,是页对齐地址,
-
* 是具体的读取NandFlash的起始地址,包括页内地址
-
* size : 复制的字节数,是页空间整数倍,也就是说,size是2K整数倍
-
*注意 : 1,write是相对于cpu说的,cpu写数据到nand中,SDRAM中的数据拷贝到Nand中。
-
* 2,坏块的话写进去的东西肯定是不对的。一般情况下如果你手上的系统支持NAND运行,
-
* 那么相关的读取数据代码中肯定有跳坏块处理的
-
*/
-
unsigned int nand_write_oob(unsigned char * buf, unsigned long start_addr, int size)
-
{
-
unsigned long ret = 0;
-
unsigned long col, page;
-
unsigned long i = 0;
-
-
for(i=start_addr; i < (start_addr + size);)
-
{
-
col = start_addr & (2048 - 1); //如果start_addr是页对齐地址,则col肯定是0
-
page = start_addr / 2048; //截取高位,是第几个页
-
-
ret = nand_write_oob_page(buf, page);
-
if(ret == 0) //写成功
-
{
-
i = i + 2048; //跳过坏块
-
buf = buf + 2048; //继续写
-
}
-
else if(ret == 1) //是坏块
-
{
-
i = i + 2048; //跳过坏块
-
buf = buf; //再写,buf不增加
-
}
-
else if(ret == 2) //写失败,但是标注坏块成功,判断是坏块
-
{
-
//这里应该打印一句报错信息
-
i = i + 2048; //跳过坏块
-
buf = buf; //再写,buf不增加
-
}
-
else if(ret == 3) //写失败,标注坏块也失败,读取的时候,无法判断是否是坏块,这里当做好块
-
{
-
//这里应该打印一句报错信息
-
i = i; //当做好块,再写一次
-
buf = buf; //再写,buf不增加
-
}
-
}
-
return 0;
-
}
-
-
-
/*功能 : 识别坏块的写 : 将数据 从SDRAM的buf 复制到 NandFlash第page_number页开始的位置。
-
* 一次写且仅写一页(2K)个字节的数据
-
* 数据方向:(SDRAM的buf--->nandflash的第page_number页)
-
*参数 : buf : SDRAM的缓冲区起始地址
-
* page_number : 是页编号,是第几页,从第0页开始
-
*注意 : 1,write是相对于cpu说的,cpu写数据到nand中, buf中的数据拷贝到Nand中。
-
* 2,写操作是以页为单位进行的,并且一次只写一页数据
-
* 3,本程序检查坏块,标记坏块,并且将ECC码记录在OOB区。
-
*/
-
unsigned int nand_write_oob_page(unsigned char * buf, unsigned long page_number)
-
{
-
unsigned long i;
-
unsigned long mecc0; //保存 NFMECC0 寄存器的值,32位值均有效,即4个字节:ECC0+ECC1+ECC2+ECC3
-
unsigned long secc; //保存 NFSECC 寄存器的值,低16位值有效,即2个字节:spare区ECC0+spare区ECC1
-
unsigned char stat;
-
unsigned int flag; //坏块标志,标注成功与否的标志
-
unsigned char ECCBuf[6]; //用来保存 上面的6个字节:mecc0+secc
-
-
//判断该块是否为坏块
-
flag = nand_IsBadBlock(page_number>>6);
-
if(1 == flag)
-
{
-
return 1; //是坏块,返回
-
}
-
//复位ECC
-
NF_RSTECC();
-
//解锁main区的ECC : 以便产生该区的ECC码
-
NF_MECC_UnLock();
-
//1,选中,片选
-
NF_CE_L();
-
//清除RB
-
NF_CLEAR_RB();
-
//2. 发出写命令0x80,写NandFlash时的第1个命令 : NandFlash写准备
-
NF_CMD(CMD_WRITE1);
-
//3. 发出地址,写地址(分5步发出)
-
NF_ADDR(0x00);
-
NF_ADDR(0x00);
-
NF_ADDR(page_number & 0xff);
-
NF_ADDR((page_number >>8) & 0xff);
-
NF_ADDR((page_number >>16) & 0xff);
-
//4, 写数据 : 一般1次写1页的数据
-
for(i=0; i<2048; i++)
-
{
-
//1次写8位的数据给NandFlash的8位IO口
-
//注意:每次写这个寄存器,即执行一次下面的语句,都会启动一次nandflash的写数据操作,
-
//前一次写数据操作不完成,是不会启动下一次写数据操作的
-
NF_WRDATA8(buf[i]);
-
//buf++;
-
}
-
//注意 : main区写完数据之后,硬件将自动产生main区的ECC码,共4个字节。
-
//锁定main区的ECC值 : 会自动把产生的main区的ECC码,保存到相应的寄存器(NFMECC0)中。
-
NF_MECC_Lock();
-
//读取main区的ECC校验码,保存到本地变量mecc0中。
-
mecc0=rNFMECC0;
-
//把ECC校验码由字型转换为字节型,并保存到字节变量数组ECCBuf中
-
ECCBuf[0] = (unsigned char)(mecc0 & 0xff); //0~7位
-
ECCBuf[1] = (unsigned char)((mecc0 >> 8) & 0xff); //8~15位
-
ECCBuf[2] = (unsigned char)((mecc0 >> 16) & 0xff); //16~23位
-
ECCBuf[3] = (unsigned char)((mecc0 >> 24) & 0xff); //24~31位
-
-
//解锁spare区的ECC :
-
NF_SECC_UnLock();
-
//把main区的ECC值写入到spare区的前4个字节地址内,即第2048~2051地址
-
for(i=0; i<4; i++)
-
{
-
//注意 : 页内main区写满之后,继续写,会写到本页的第2049个地址,即开始写spare区的第1个字节。
-
NF_WRDATA8(ECCBuf[i]);
-
}
-
//注意 : spare区写完数据之后,硬件将自动产生spare区的ECC码,共2个字节。
-
//锁定spare区的ECC值 : 会自动把产生的spare区的ECC码,保存到相应的寄存器(NFSECC)中。
-
NF_SECC_Lock();
-
-
//读取spare区的ECC校验码,保存到本地变量secc中。
-
secc = rNFSECC;
-
//把ECC校验码保存到字节变量数组ECCBuf中
-
ECCBuf[4]=(unsigned char)(secc & 0xff); //0~7位
-
ECCBuf[5]=(unsigned char)((secc >> 8) & 0xff); //8~15位
-
//把spare区的ECC值继续写入到spare区,第2052~2053地址内。
-
//注意 : 每块的第0页的spare区,第2054个字节处,会有坏块标志,其他页没有。
-
for(i=4; i<6; i++)
-
{
-
NF_WRDATA8(ECCBuf[i]);
-
}
-
//5. 发出读命令0x10,写NandFlash时的第2个命令 : 启动写操作
-
NF_CMD(CMD_WRITE2);
-
for(i=0; i<100; i++); //延时一段时间,以等待写操作完成
-
//6. 判断状态,写入读状态寄存器的命令 : 用来判断当前写操作是否完成,是否成功
-
NF_CMD(CMD_STATUS);
-
do
-
{
-
//读取8位状态数据
-
stat = NF_RDDATA8();
-
}
-
while(!(stat & 0x40)); //等到第6位为1 : 表示写数据完成。
-
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
-
-
//7. 取消选中,关闭片选
-
NF_CE_H();
-
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
-
if(stat & 0x1)
-
{
-
//如果写入失败,则标记坏块。
-
//A12~A17,可以寻址到block块下面的page页; A18~A28,可以寻址到block块。
-
//右移6位,表示去掉 A12~A17,只剩下 块地址 A18~A28 共11位的有效值。
-
flag = nand_MarkBadBlock(page_number>>6);
-
if(0 == flag)
-
{
-
return 2; //标注坏块成功,写入操作失败
-
}
-
else
-
{
-
return 3; //标注坏块失败,写入操作失败
-
}
-
}
-
else
-
{
-
return 0; //写操作成功
-
}
-
}
-
-
-
/*
-
*函数功能 : 检查指定的块是否是坏块
-
*函数参数 : 块的编号,从第0块开始
-
*函数返回值 : 0 : 好块 1 : 坏块
-
*注意 :
-
* 1, 为了和固有坏块信息保持一致,将新发现的坏块的第1个page的 spare area的第6个Byte标记为非0xff的值。
-
* 我们定义在spare区的第6个地址(即每页的第2054地址)用来标注坏块,0x44表示该块为坏块
-
* 要判断坏块时,利用随意读命令来读取2054地址的内容是否为0x44,正常是0xff
-
* 2, 坏块的特性是:当编程/擦除这个块时,不能将某些位拉高,这 会造成Page Program和
-
* Block Erase操作时的错误,相应地反映到Status Register的相应位。
-
* 我们去查看 Status Register 的相应位,我们就可以找到这个坏块是怎么来的了。即可知是什么原因造成的坏块。
-
* 3, NAND Flash出厂时在OOB中已经反映出了坏块信息,因此,如果在擦除一个块之前,
-
* 一定要先check一下OOB的第6个byte是否是0xff,如果是就证明这是一个好块,
-
* 可以擦除;如果是非0xff,那么就不能擦除。
-
* 当然,这样处理可能会犯一个错误―――“错杀伪坏块”,因为在芯片操作过程中可能
-
* 由于电压不稳定等偶然因素会造成NAND操作的错误。但是,为了数据的可 靠性及软件设计
-
* 的简单化,我们就要奉行“蒋委员长”的“宁可错杀一千,也决不放过一个”的宗旨。
-
* 4, 如果在对一个块的某个page进行编程的时候发生了错误就要把这个块标记为坏块,
-
* 首先就要把其他好的page里面的内容备份到另外一个空的好块里面,然后,把这个块标记为
-
* 坏块。
-
* 5, 当然,这可能会犯“错杀”之误,一个补救的办法,就是在进行完页备份之后,
-
* 再将这个块擦除一遍,如果Block Erase发生错误,那就证明这个块是个真正的坏块,那就毫不犹豫地将它打个“戳”吧!
-
* 6, 1个块有128K,64个页,为什么是第1页的OOB区的第6个Byte?这个是各个厂家默认的规定。
-
* 即这是NAND Flash生产商的默认约定,你可以看到Samsung,Toshiba,STMicroelectronics都是使用这个Byte作为坏块标记的。
-
*/
-
unsigned int nand_IsBadBlock(unsigned long block)
-
{
-
unsigned char ch;
-
-
//如果是第0块,0*64=0,则第1个参数是第0页
-
//如果是第1块,1*64=64,则第1个参数是第64页。
-
//第0~63页是第0块,第64页是第1块最开始的一页
-
ch = nand_read_random(block*64, 2048+6);
-
if(0xFF == ch) //是好块
-
{
-
return 0;
-
}
-
else //是坏块
-
{
-
return 1;
-
}
-
}
-
-
-
/*
-
*函数功能 : 标注坏块,标注该页所在的块为坏块
-
*函数参数 : 块的编号,从第0块开始
-
*函数返回值 :
-
* 0 : 标注坏块 成功
-
* 1 : 标注坏块 失败
-
*注意 : 1,要标注坏块时,利用随意写命令来向2048+6地址写0x33
-
* 2, 输入参数都为块地址,也就是即使仅仅一页出现问题,我们也标注整个块为坏块。
-
* 3,标注位置 : 约定是在 本块,第0页的第2054个字节处。即本块第0页的spare区的第6个字节处。其他页没有。
-
*/
-
unsigned int nand_MarkBadBlock(unsigned long block)
-
{
-
unsigned char flag;
-
-
//如果是第0块,0*64=0,则第1个参数是第0页
-
//如果是第1块,1*64=64,则第1个参数是第64页。
-
//第0~63页是第0块,第64页是第1块最开始的一页
-
flag = nand_write_random(block*64, 2048+6, 0x33);
-
if(0 == flag) //写入成功
-
{
-
return 0; //标注坏块 成功
-
}
-
else
-
{
-
return 1; //标注坏块 失败
-
}
-
}
-
-
-
/*
-
坏块管理:
-
1,我们以前没有进行坏块管理,为什么也能直接使用nandflash?
-
因为三星公司为了支持nandflash的启动,确保了第0块没有坏块,所以我们读/写/擦除第0块是不会出现问题的。
-
2,第0块之后的块,是可能出现坏块的。
-
3,本程序没有实现坏块表BBT,有时间要实现一下。
-
*/
-
-
/*
-
测试
-
-
*/
nand.h文件
-
#ifndef _NAND_H_
-
#define _NAND_H_
-
-
/*NANDFLASH配置寄存器 : NFCONF:用来设置时序参数宽度,数据位宽*/
-
#define rNFCONF (*(volatile unsigned long *)0x4E000000)
-
/*NANDFLASH控制寄存器 : NFCONT:用来禁止/使能nandflash控制器,使能/禁止片选信号*/
-
#define rNFCONT (*(volatile unsigned long *)0x4E000004)
-
/*NANDFLASH命令集寄存器 : NFCMMD:用来向nandflash发出命令信号*/
-
#define rNFCMMD (*(volatile unsigned long *)0x4E000008)
-
/*NANDFLASH地址集寄存器 : NFADDR:用来向nandflash发出地址信号*/
-
#define rNFADDR (*(volatile unsigned long *)0x4E00000C)
-
/*NANDFLASH数据寄存器 : NFDATA,32位=0x4E000010+0x4E000011+0x4E000012+0x4E000013*/
-
#define rNFDATA (*(volatile unsigned long *)0x4E000010)
-
/*截取NANDFLASH数据寄存器的8位 : 仅0x4E000010,实际上每次读写也只用到低8位,不可能用到32位。
-
每次读写此寄存器,就将启动一次对nandflash的读数据写数据的操作。*/
-
#define rNFDATA8 (*(volatile unsigned char *)0x4E000010)
-
/*NANDFLASH运行状态寄存器 : NFSTAT:只用了最低位,0:busy,1:ready*/
-
#define rNFSTAT (*(volatile unsigned long *)0x4E000020)
-
-
-
-
-
-
/************************************************************************/
-
//下面3个寄存器 : 是在读取nandflash数据的时候,需要将已有的ECC写入下面3个寄存器,已有的ECC保存在OOB区中。
-
//读取完毕之后,会自动算出一个ECC,再将这个已有的ECC和这个刚算出来的ECC比较。比较的结果会自动放在NFESTAT寄存器中。
-
/*main区ECC寄存器0,本nandflash只有8位IO口
-
只有16位数据有效,但不是连续的16位 : 0~7位:ECC0; 16~23位:ECC1*/
-
#define rNFMECCD0 (*(volatile unsigned long *)0x4e000014)
-
/*main区ECC寄存器1,本nandflash只有8位IO口
-
只有16位数据有效,但不是连续的16位 : 0~7位:ECC2; 16~23位:ECC3*/
-
#define rNFMECCD1 (*(volatile unsigned long *)0x4e000018)
-
/*spare区ECC寄存器,本nandflash只有8位IO口
-
只有16位数据有效,但不是连续的16位 : 0~7位:spare区ECC0; 16~23位:spare区ECC1*/
-
#define rNFSECCD (*(volatile unsigned long *)0x4e00001c)
-
-
/*nandflash的ECC状态寄存器0*/
-
#define rNFESTAT0 (*(volatile unsigned long *)0x4e000024)
-
/*nandflash的ECC状态寄存器1,本nandflash只有8位IO口,所以只用上面的NFESTAT0寄存器就行了,NFESTAT1是适用于nandflash有16位IO口的。*/
-
#define rNFESTAT1 (*(volatile unsigned long *)0x4e000028)
-
-
//下面3个寄存器 : 是在将数据写入nandflash的时候,硬件自动生成的ECC码,硬件自动保存的位置,我们一般再保存到OOB区中。
-
/*main区ECC寄存器0,只适用于nandflash的低8位IO口
-
32位数据均有效 : 0~7位:ECC0; 8~15位:ECC1; 16~23位:ECC2; 24~31位:ECC3*/
-
#define rNFMECC0 (*(volatile unsigned long *)0x4e00002c)
-
/*main区ECC寄存器1,本nandflash只有8位IO口,所以只用上面的NFMECC0寄存器就行了,NFMECC1是适用于nandflash有16位IO口的。*/
-
#define rNFMECC1 (*(volatile unsigned long *)0x4e000030)
-
/*spare区ECC寄存器,本nandflash只有8位IO口,只用本寄存器的低2个字节,高2个字节是给16位IO的nandflash用的。
-
低16位数据有效 : 0~7位:spare区ECC0; 8~15位:spare区ECC1*/
-
#define rNFSECC (*(volatile unsigned long *)0x4e000034)
-
/************************************************************************/
-
-
-
-
-
-
/*相关命令如下 : */
-
/*读NandFlash页时的命令1*/
-
#define CMD_READ1 0x00
-
/*读NandFlash页时的命令2*/
-
#define CMD_READ2 0x30
-
-
/*页编程 : 写NandFlash页时的命令1*/
-
#define CMD_WRITE1 0x80
-
/*页编程 : 写NandFlash页时的命令2*/
-
#define CMD_WRITE2 0x10
-
-
/*擦除NandFlash块的命令1*/
-
#define CMD_ERASE1 0x60
-
/*擦除NandFlash块的命令2*/
-
#define CMD_ERASE2 0xD0
-
-
/*读状态寄存器的命令*/
-
#define CMD_STATUS 0x70
-
/*读取芯片ID的命令*/
-
#define CMD_READID 0x90
-
/*重启命令*/
-
#define CMD_RESET 0xFF
-
-
-
-
-
/*随意读命令周期1*/
-
#define CMD_RANDOMREAD1 0x05
-
/*随意读命令周期2*/
-
#define CMD_RANDOMREAD2 0xE0
-
/*随意写命令*/
-
#define CMD_RANDOMWRITE 0x85
-
-
-
-
-
#define NF_CE_L() {(rNFCONT) &= (~(1<<1));}
-
/*禁止nandfalsh : 控制寄存器NFCONT[1]=1*/
-
#define NF_CE_H() {(rNFCONT) |= (1<<1);}
-
-
/*向存储器发出命令 : 命令寄存器NFCMMD[7:0]*/
-
#define NF_CMD(data) {(rNFCMMD) = (data);}
-
/*向存储器发出地址值 : 地址寄存器NFADDR[7:0]*/
-
#define NF_ADDR(data) {(rNFADDR) = (data);}
-
-
/*等待NandFlash准备就绪(即不忙) : 一直等到状态寄存器NFSTAT[0]=1,循环才退出*/
-
#define NF_WAIT_RB() {while(!((rNFSTAT) & (1<<0)));}
-
/*清除RB:即检测RB传输*/
-
#define NF_CLEAR_RB() {(rNFSTAT) |= (1<<2);}
-
/*等待检测RnB : 一直等到状态寄存器NFSTAT[2]=1,循环才退出*/
-
#define NF_DETECT_RB {while(!((rNDSTA) & (1<<2)));}
-
-
/*从NANDFLASH的IO口中读数据(32位) : 数据寄存器NFDATA*/
-
#define NF_RDDATA() (rNFDATA)
-
/*从NANDFLASH的IO口中读数据(8位) : 数据寄存器NFDATA*/
-
#define NF_RDDATA8() (rNFDATA8)
-
/*向NANDFLASH的IO口中写数据(32位) : 数据寄存器NFDATA*/
-
#define NF_WRDATA(data) {(rNFDATA) = (data);}
-
/*向NANDFLASH的IO口中写数据(8位) : 数据寄存器NFDATA*/
-
#define NF_WRDATA8(data) {(rNFDATA8) = (data);}
-
-
-
-
-
/************************************************************************/
-
/*复位ECC:初始化ECC编码器/译码器*/
-
#define NF_RSTECC() {rNFCONT |= (1<<4);}
-
/*解锁main区ECC*/
-
#define NF_MECC_UnLock() {rNFCONT &= (~(1<<5));}
-
/*锁定main区ECC*/
-
#define NF_MECC_Lock() {rNFCONT |= (1<<5);}
-
/*解锁spare区(OOB区)ECC*/
-
#define NF_SECC_UnLock() {rNFCONT &= (~(1<<6));}
-
/*锁定spare区(OOB区)ECC*/
-
#define NF_SECC_Lock() {rNFCONT |= (1<<6);}
-
/************************************************************************/
-
-
-
-
-
/*时序信号参数的宽度*/
-
#define TACLS 1
-
#define TWRPH0 3
-
#define TWRPH1 0
-
-
int isBootFromNorFlash(void);
-
void nand_reset();
-
void nand_init();
-
unsigned char nand_readId();
-
void write_addr(unsigned long addr);
-
void nand_read(unsigned char *buf, unsigned long start_addr, int size);
-
void nand_read_1(unsigned char *buf, unsigned long start_addr, int size);
-
int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size);
-
-
unsigned int nand_read2(unsigned char * buf, unsigned long start_addr, int size);
-
unsigned char nand_read_random(unsigned long page_number, unsigned long addr);
-
unsigned int nand_read_oob(unsigned char *buf, unsigned long start_addr, int size);
-
unsigned int nand_read_oob_page(unsigned char * buf, unsigned long page_number);
-
-
unsigned char nand_erase(unsigned long block_number);
-
unsigned int nand_erase_oob(unsigned long block_number);
-
-
unsigned int nand_write(unsigned char *buf, unsigned long start_addr, int size);
-
unsigned int nand_write_random(unsigned long page_number, unsigned long addr, unsigned char data);
-
unsigned int nand_write_oob(unsigned char *buf, unsigned long start_addr, int size);
-
unsigned int nand_write_oob_page(unsigned char * buf, unsigned long page_number);
-
-
unsigned int nand_IsBadBlock(unsigned long block);
-
unsigned int nand_MarkBadBlock(unsigned long block);
-
-
#endif
setup.c文件
-
#include "setup.h"
-
-
int mystrlen(char *str)
-
{
-
int i = 0;
-
while (str[i])
-
{
-
i++;
-
}
-
return i;
-
}
-
-
void mystrcpy(char *dest, char *src)
-
{
-
while ((*dest++ = *src++) != '\0');
-
}
-
-
/*
-
*功能:使能ICache
-
*/
-
void icache_enable()
-
{
-
unsigned int temp = 1<<12;
-
-
asm(
-
"mrc p15,0,r0,c1,c0,0\n" /*读出控制寄存器c1的值到r0中*/
-
"orr r0,r0,%0\n" /*第12位I位置1,即使能ICache*/
-
"mcr p15,0,r0,c1,c0,0\n" /*将修改后的值写入控制寄存器*/
-
:
-
:"r"(temp)
-
:"r0"
-
);
-
}
-
-
-
/*
-
*功能:禁止ICache
-
*/
-
void icache_disable()
-
{
-
unsigned int temp = 1<<12;
-
-
asm( /*GCC内联汇编函数*/
-
"mrc p15,0,r0,c1,c0,0\n"
-
"bic r0,r0,%0\n" /*第12位I位置0,即禁止ICache*/
-
"mcr p15,0,r0,c1,c0,0\n"
-
:
-
:"r"(temp)
-
:"r0"
-
);
-
}
setup.h文件
-
#ifndef _SETUP_H_
-
#define _SETUP_H_
-
-
int mystrlen(char *str);
-
void mystrcpy(char *dest, char *src);
-
-
void icache_enable();
-
void icache_disable();
-
-
#endif
string.c文件
-
#include "string.h"
-
#include "uart.h"
-
-
char * ___strtok;
-
-
/**
-
* strcpy - Copy a %NUL terminated string
-
* @dest: Where to copy the string to
-
* @src: Where to copy the string from
-
*/
-
char * strcpy(char * dest,const char *src)
-
{
-
char *tmp = dest;
-
-
while ((*dest++ = *src++) != '\0')
-
/* nothing */;
-
return tmp;
-
}
-
-
/**
-
* strncpy - Copy a length-limited, %NUL-terminated string
-
* @dest: Where to copy the string to
-
* @src: Where to copy the string from
-
* @count: The maximum number of bytes to copy
-
*
-
* Note that unlike userspace strncpy, this does not %NUL-pad the buffer.
-
* However, the result is not %NUL-terminated if the source exceeds
-
* @count bytes.
-
*/
-
char * strncpy(char * dest,const char *src,unsigned int count)
-
{
-
char *tmp = dest;
-
-
while (count-- && (*dest++ = *src++) != '\0')
-
/* nothing */;
-
-
return tmp;
-
}
-
-
/**
-
* strcat - Append one %NUL-terminated string to another
-
* @dest: The string to be appended to
-
* @src: The string to append to it
-
*/
-
char * strcat(char * dest, const char * src)
-
{
-
char *tmp = dest;
-
-
while (*dest)
-
dest++;
-
while ((*dest++ = *src++) != '\0')
-
;
-
-
return tmp;
-
}
-
-
/**
-
* strncat - Append a length-limited, %NUL-terminated string to another
-
* @dest: The string to be appended to
-
* @src: The string to append to it
-
* @count: The maximum numbers of bytes to copy
-
*
-
* Note that in contrast to strncpy, strncat ensures the result is
-
* terminated.
-
*/
-
char * strncat(char *dest, const char *src, unsigned int count)
-
{
-
char *tmp = dest;
-
-
if (count) {
-
while (*dest)
-
dest++;
-
while ((*dest++ = *src++)) {
-
if (--count == 0) {
-
*dest = '\0';
-
break;
-
}
-
}
-
}
-
-
return tmp;
-
}
-
-
/**
-
* strcmp - Compare two strings
-
* @cs: One string
-
* @ct: Another string
-
*/
-
int strcmp(const char * cs,const char * ct)
-
{
-
register signed char __res;
-
-
while (1) {
-
if ((__res = *cs - *ct++) != 0 || !*cs++)
-
break;
-
}
-
-
return __res;
-
}
-
-
/**
-
* strncmp - Compare two length-limited strings
-
* @cs: One string
-
* @ct: Another string
-
* @count: The maximum number of bytes to compare
-
*/
-
int strncmp(const char * cs,const char * ct,unsigned int count)
-
{
-
register signed char __res = 0;
-
-
while (count) {
-
if ((__res = *cs - *ct++) != 0 || !*cs++)
-
break;
-
count--;
-
}
-
-
return __res;
-
}
-
-
/**
-
* strchr - Find the first occurrence of a character in a string
-
* @s: The string to be searched
-
* @c: The character to search for
-
*/
-
char * strchr(const char * s, int c)
-
{
-
for(; *s != (char) c; ++s)
-
if (*s == '\0')
-
return NULL;
-
return (char *) s;
-
}
-
-
/**
-
* strrchr - Find the last occurrence of a character in a string
-
* @s: The string to be searched
-
* @c: The character to search for
-
*/
-
char * strrchr(const char * s, int c)
-
{
-
const char *p = s + strlen(s);
-
do {
-
if (*p == (char)c)
-
return (char *)p;
-
} while (--p >= s);
-
return NULL;
-
}
-
-
/**
-
* strlen - Find the length of a string
-
* @s: The string to be sized
-
*/
-
unsigned int strlen(const char * s)
-
{
-
const char *sc;
-
-
for (sc = s; *sc != '\0'; ++sc)
-
/* nothing */;
-
return sc - s;
-
}
-
-
/**
-
* strnlen - Find the length of a length-limited string
-
* @s: The string to be sized
-
* @count: The maximum number of bytes to search
-
*/
-
unsigned int strnlen(const char * s, unsigned int count)
-
{
-
const char *sc;
-
-
for (sc = s; count-- && *sc != '\0'; ++sc)
-
/* nothing */;
-
return sc - s;
-
}
-
-
/**
-
* strspn - Calculate the length of the initial substring of @s which only
-
* contain letters in @accept
-
* @s: The string to be searched
-
* @accept: The string to search for
-
*/
-
unsigned int strspn(const char *s, const char *accept)
-
{
-
const char *p;
-
const char *a;
-
unsigned int count = 0;
-
-
for (p = s; *p != '\0'; ++p) {
-
for (a = accept; *a != '\0'; ++a) {
-
if (*p == *a)
-
break;
-
}
-
if (*a == '\0')
-
return count;
-
++count;
-
}
-
-
return count;
-
}
-
-
/**
-
* strpbrk - Find the first occurrence of a set of characters
-
* @cs: The string to be searched
-
* @ct: The characters to search for
-
*/
-
char * strpbrk(const char * cs,const char * ct)
-
{
-
const char *sc1,*sc2;
-
-
for( sc1 = cs; *sc1 != '\0'; ++sc1) {
-
for( sc2 = ct; *sc2 != '\0'; ++sc2) {
-
if (*sc1 == *sc2)
-
return (char *) sc1;
-
}
-
}
-
return NULL;
-
}
-
-
/**
-
* strtok - Split a string into tokens
-
* @s: The string to be searched
-
* @ct: The characters to search for
-
*
-
* WARNING: strtok is deprecated, use strsep instead.
-
*/
-
char * strtok(char * s,const char * ct)
-
{
-
char *sbegin, *send;
-
-
sbegin = s ? s : ___strtok;
-
if (!sbegin) {
-
return NULL;
-
}
-
sbegin += strspn(sbegin,ct);
-
if (*sbegin == '\0') {
-
___strtok = NULL;
-
return( NULL );
-
}
-
send = strpbrk( sbegin, ct);
-
if (send && *send != '\0')
-
*send++ = '\0';
-
___strtok = send;
-
return (sbegin);
-
}
-
-
/**
-
* strsep - Split a string into tokens
-
* @s: The string to be searched
-
* @ct: The characters to search for
-
*
-
* strsep() updates @s to point after the token, ready for the next call.
-
*
-
* It returns empty tokens, too, behaving exactly like the libc function
-
* of that name. In fact, it was stolen from glibc2 and de-fancy-fied.
-
* Same semantics, slimmer shape. ;)
-
*/
-
char * strsep(char **s, const char *ct)
-
{
-
char *sbegin = *s, *end;
-
-
if (sbegin == NULL)
-
return NULL;
-
-
end = strpbrk(sbegin, ct);
-
if (end)
-
*end++ = '\0';
-
*s = end;
-
-
return sbegin;
-
}
-
-
/**
-
* memset - Fill a region of memory with the given value
-
* @s: Pointer to the start of the area.
-
* @c: The byte to fill the area with
-
* @count: The size of the area.
-
*
-
* Do not use memset() to access IO space, use memset_io() instead.
-
*/
-
void * memset(void * s,int c,unsigned int count)
-
{
-
char *xs = (char *) s;
-
-
while (count--)
-
*xs++ = c;
-
-
return s;
-
}
-
-
/**
-
* bcopy - Copy one area of memory to another
-
* @src: Where to copy from
-
* @dest: Where to copy to
-
* @count: The size of the area.
-
*
-
* Note that this is the same as memcpy(), with the arguments reversed.
-
* memcpy() is the standard, bcopy() is a legacy BSD function.
-
*
-
* You should not use this function to access IO space, use memcpy_toio()
-
* or memcpy_fromio() instead.
-
*/
-
void bcopy(const void *src, void *dest, unsigned int count)
-
{
-
char *destTmp = (char *)dest;
-
char *srcTmp = (char *)src;
-
-
while (count--)
-
*destTmp++ = *srcTmp++;
-
}
-
-
/**
-
* memcpy - Copy one area of memory to another
-
* @dest: Where to copy to
-
* @src: Where to copy from
-
* @count: The size of the area.
-
*
-
* You should not use this function to access IO space, use memcpy_toio()
-
* or memcpy_fromio() instead.
-
*/
-
void * memcpy(void * dest,const void *src,unsigned int count)
-
{
-
char *tmp = (char *) dest, *s = (char *) src;
-
-
while (count--)
-
*tmp++ = *s++;
-
-
return dest;
-
}
-
-
/**
-
* memmove - Copy one area of memory to another
-
* @dest: Where to copy to
-
* @src: Where to copy from
-
* @count: The size of the area.
-
*
-
* Unlike memcpy(), memmove() copes with overlapping areas.
-
*/
-
void * memmove(void * dest,const void *src,unsigned int count)
-
{
-
char *tmp, *s;
-
-
if (dest <= src) {
-
tmp = (char *) dest;
-
s = (char *) src;
-
while (count--)
-
*tmp++ = *s++;
-
}
-
else {
-
tmp = (char *) dest + count;
-
s = (char *) src + count;
-
while (count--)
-
*--tmp = *--s;
-
}
-
-
return dest;
-
}
-
-
/**
-
* memcmp - Compare two areas of memory
-
* @cs: One area of memory
-
* @ct: Another area of memory
-
* @count: The size of the area.
-
*/
-
int memcmp(const void * cs,const void * ct,unsigned int count)
-
{
-
const unsigned char *su1, *su2;
-
int res = 0;
-
-
for( su1 = (const unsigned char *)cs, su2 = (const unsigned char *)ct; 0 < count; ++su1, ++su2, count--)
-
if ((res = *su1 - *su2) != 0)
-
break;
-
return res;
-
}
-
-
/**
-
* memscan - Find a character in an area of memory.
-
* @addr: The memory area
-
* @c: The byte to search for
-
* @size: The size of the area.
-
*
-
* returns the address of the first occurrence of @c, or 1 byte past
-
* the area if @c is not found
-
*/
-
void * memscan(void * addr, int c, unsigned int size)
-
{
-
unsigned char * p = (unsigned char *) addr;
-
-
while (size) {
-
if (*p == c)
-
return (void *) p;
-
p++;
-
size--;
-
}
-
return (void *) p;
-
}
-
-
/**
-
* strstr - Find the first substring in a %NUL terminated string
-
* @s1: The string to be searched
-
* @s2: The string to search for
-
*/
-
char * strstr(const char * s1,const char * s2)
-
{
-
int l1, l2;
-
-
l2 = strlen(s2);
-
if (!l2)
-
return (char *) s1;
-
l1 = strlen(s1);
-
while (l1 >= l2) {
-
l1--;
-
if (!memcmp(s1,s2,l2))
-
return (char *) s1;
-
s1++;
-
}
-
return NULL;
-
}
-
-
/**
-
* memchr - Find a character in an area of memory.
-
* @s: The memory area
-
* @c: The byte to search for
-
* @n: The size of the area.
-
*
-
* returns the address of the first occurrence of @c, or %NULL
-
* if @c is not found
-
*/
-
void *memchr(const void *s, int c, unsigned int n)
-
{
-
const unsigned char *p = (const unsigned char *)s;
-
while (n-- != 0) {
-
if ((unsigned char)c == *p++) {
-
return (void *)(p-1);
-
}
-
}
-
return NULL;
-
}
-
-
/*功能:向字符串 格式化打印一个字符串
-
*参数:格式化的字符串
-
*注意:这个是简易版本 (%02x 没法完成)
-
*/
-
int sprintf(char * str, const char *fmt, ...)
-
{
-
int count = 0;
-
char c;
-
char *s;
-
int n;
-
char buf[65];
-
va_list ap;
-
-
va_start(ap, fmt);
-
-
while(*fmt != '\0')
-
{
-
if(*fmt == '%')
-
{
-
fmt++;
-
switch(*fmt)
-
{
-
case 'd': /*整型*/
-
n = va_arg(ap, int);
-
if(n < 0)
-
{
-
*str = '-';
-
str++;
-
n = -n;
-
}
-
itoa(n, buf);
-
memcpy(str, buf, strlen(buf));
-
str += strlen(buf);
-
-
break;
-
case 'c': /*字符型*/
-
c = va_arg(ap, int);
-
*str = c;
-
str++;
-
-
break;
-
case 'x': /*16进制*/
-
n = va_arg(ap, int);
-
xtoa(n, buf);
-
memcpy(str, buf, strlen(buf));
-
str += strlen(buf);
-
-
break;
-
case 's': /*字符串*/
-
s = va_arg(ap, char *);
-
memcpy(str, s, strlen(s));
-
str += strlen(s);
-
-
break;
-
case '%': /*输出%*/
-
*str = '%';
-
str++;
-
-
break;
-
default:
-
break;
-
}
-
}
-
else
-
{
-
*str = *fmt;
-
str++;
-
-
if(*fmt == '\n')
-
{
-
//uart_sendByte('\r');
-
}
-
}
-
fmt++;
-
}
-
-
va_end(ap);
-
-
return count;
-
}
-
-
-
/*
-
*功能:整型(int) 转化成 字符型(char)
-
*注意:不用 % / 符号的话,只能正确打印:0...9的数字对应的字符'0'...'9'
-
*/
-
void itoa(unsigned int n, char * buf)
-
{
-
int i;
-
-
if(n < 10)
-
{
-
buf[0] = n + '0';
-
buf[1] = '\0';
-
return;
-
}
-
itoa(n / 10, buf);
-
-
for(i=0; buf[i]!='\0'; i++);
-
-
buf[i] = (n % 10) + '0';
-
-
buf[i+1] = '\0';
-
}
-
-
/*
-
*功能:字符型(char) 转化成 整型(int)
-
*/
-
int atoi(char* pstr)
-
{
-
int int_ret = 0;
-
int int_sign = 1; //正负号标示 1:正数 -1:负数
-
-
if(pstr == '\0') //判断指针是否为空
-
{
-
uart_printf("Pointer is NULL\n");
-
return -1;
-
}
-
while(((*pstr) == ' ') || ((*pstr) == '\n') || ((*pstr) == '\t') || ((*pstr) == '\b'))
-
{
-
pstr++; //跳过前面的空格字符
-
}
-
-
/*
-
* 判断正负号
-
* 如果是正号,指针指向下一个字符
-
* 如果是符号,把符号标记为Integer_sign置-1,然后再把指针指向下一个字符
-
*/
-
if(*pstr == '-')
-
{
-
int_sign = -1;
-
}
-
if(*pstr == '-' || *pstr == '+')
-
{
-
pstr++;
-
}
-
-
while(*pstr >= '0' && *pstr <= '9') //把数字字符串逐个转换成整数,并把最后转换好的整数赋给Ret_Integer
-
{
-
int_ret = int_ret * 10 + *pstr - '0';
-
pstr++;
-
}
-
int_ret = int_sign * int_ret;
-
-
return int_ret;
-
}
-
-
/*
-
*功能:16进制字(0x) 转化成 字符型(char)
-
*注意:不用 % / 符号的话,只能正确打印,0...9..15的数字,对应的'0'...'9''A'...'F'
-
*注意:由于编译问题,这个函数,暂时由uart_sendByte_hex()函数替代
-
*/
-
void xtoa(unsigned int n, char * buf)
-
{
-
int i;
-
-
if(n < 16)
-
{
-
if(n < 10)
-
{
-
buf[0] = n + '0';
-
}
-
else
-
{
-
buf[0] = n - 10 + 'a';
-
}
-
buf[1] = '\0';
-
return;
-
}
-
xtoa(n / 16, buf);
-
-
for(i = 0; buf[i] != '\0'; i++);
-
-
if((n % 16) < 10)
-
{
-
buf[i] = (n % 16) + '0';
-
}
-
else
-
{
-
buf[i] = (n % 16) - 10 + 'a';
-
}
-
buf[i + 1] = '\0';
-
}
-
-
/*
-
* 判断一个字符是否数字
-
*/
-
int isDigit(unsigned char c)
-
{
-
if (c >= '0' && c <= '9')
-
return 1;
-
else
-
return 0;
-
}
-
-
/*
-
* 判断一个字符是否英文字母
-
*/
-
int isLetter(unsigned char c)
-
{
-
if (c >= 'a' && c <= 'z')
-
return 1;
-
else if (c >= 'A' && c <= 'Z')
-
return 1;
-
else
-
return 0;
-
}
-
-
-
/*
-
研究数据结构的时候,还有很多字符串处理函数
-
*/
string.h文件
-
#ifndef _STRING_H_
-
#define _STRING_H_
-
-
typedef char * va_list;
-
#define va_start(ap,p) (ap = (char *) (&(p)+1))
-
#define va_arg(ap, type) ((type *) (ap += sizeof(type)))[-1]
-
#define va_argp(ap, type) ((type *) (ap += sizeof(type)))-1
-
#define va_end(ap)
-
-
#define NULL 0
-
-
char * strcpy(char * dest,const char *src);
-
char * strncpy(char * dest,const char *src,unsigned int count);
-
char * strcat(char * dest, const char * src);
-
char * strncat(char *dest, const char *src, unsigned int count);
-
int strcmp(const char * cs,const char * ct);
-
int strncmp(const char * cs,const char * ct,unsigned int count);
-
char * strchr(const char * s, int c);
-
char * strrchr(const char * s, int c);
-
unsigned int strlen(const char * s);
-
unsigned int strnlen(const char * s, unsigned int count);
-
unsigned int strspn(const char *s, const char *accept);
-
char * strpbrk(const char * cs,const char * ct);
-
char * strtok(char * s,const char * ct);
-
char * strsep(char **s, const char *ct);
-
void * memset(void * s,int c,unsigned int count);
-
void bcopy(const void *src, void *dest, unsigned int count);
-
void * memcpy(void * dest,const void *src,unsigned int count);
-
void * memmove(void * dest,const void *src,unsigned int count);
-
int memcmp(const void * cs,const void * ct,unsigned int count);
-
void * memscan(void * addr, int c, unsigned int size);
-
char * strstr(const char * s1,const char * s2);
-
void *memchr(const void *s, int c, unsigned int n);
-
int sprintf(char * str, const char *fmt, ...);
-
void itoa(unsigned int n, char * buf);
-
int atoi(char* pstr);
-
void xtoa(unsigned int n, char * buf);
-
int isDigit(unsigned char c);
-
int isLetter(unsigned char c);
-
-
#endif
uart.c文件
-
#include "string.h"
-
#include "uart.h"
-
-
-
/***********************************************************************************/
-
//串口相关函数
-
/*功能:初始化串口UART0
-
*轮询方式来使用串口
-
*/
-
void init_uart(void)
-
{
-
/*GPHCON寄存器
-
[7:6]=10,GPH2作为TXD0,串口发送数据引脚
-
[5:4]=10,GPH3作为RXD0,串口接受数据引脚
-
GPH2,GPH3控制串口通道0
-
GPH4,GPH5控制串口通道1
-
GPH6,GPH7控制串口通道2
-
*/
-
rGPHCON = ((2<<6)|(2<<4)|(0<<2)|(0<<0));
-
rGPHUP = ((1<<3)|(1<<2)); /*禁止上拉*/
-
-
/*ULCON0串口线路控制寄存器:设置传输格式
-
[6]=0,普通模式,非红外工作模式
-
[5:3]=000,无奇偶校验位,100=奇校验,101=偶校验,110=固定校验位为1,111=固定校验位为0
-
[2]=0,每帧1个停止位,1:每帧2个停止位
-
[1:0]=11,8位,每帧发送或接受的数据位的个数。00=5位,01=6位,10=7位
-
*/
-
rULCON0 = 0x3;
-
-
/*UCON0:串口控制寄存器:用于选择UART时钟源,设置UART中断方式
-
[15:12]: 当[11:10]选择PCLK时,这4位无效。选择FCLK/n时,表示n值。
-
[11:10]=00: 选择PCLK给UART比特率,10:PCLK, 01:UEXTCLK, 11:FCLK/n。
-
[9]=1: 中断请求类型
-
[8]=0: 中断请求类型
-
[7]=0: 禁止超时中断,1:使能超时中断
-
[6]=1: 产生接受错误状态中断,1:不产生接受错误状态中断
-
[5]=0: 正常操作,非回环模式。发送引脚发送的数据,直接到达接受引脚,一般是测试用。
-
[4]=0: 正常操作,不发送断电信号
-
[3:2]=01: 发送模式,中断或者轮询,如果中断寄存器不设置,就是轮询。
-
[1:0]=01: 接受模式,中断或者轮询
-
*/
-
rUCON0 = 0x5;
-
-
/*UFCON:串口FIFO控制寄存器:用于设置是否使用FIFO,设置各FIFO的触发阀值。
-
[7:6]=发送FIFO的触发深度,即在发送时,发送FIFO中还剩有多少个数据时,才触发中断,告诉CPU可以继续发送了。
-
[5:4]=接受FIFO的触发深度,即在接受时,接受FIFO中接受了多少个数据后,才触发中断,告诉CPU已经有接受到的数据了。
-
[3]=空
-
[2]=TX的FIFO是否复位
-
[1]=RX的FIFO是否复位
-
[0]=0,不使用FIFO,非FIFO模式,相当于接受和发送时,不使用缓冲寄存器,上面的设置无效。如果这里是1,则上面的位肯定有对应的设置。
-
*/
-
rUFCON0 = 0x0;
-
-
/*UMCON:串口MODEM控制寄存器:用于流量控制
-
[7:5]=000,规定必须为0
-
[4]=0:禁止自动流控制(AFC),即不使用流控。一般不使用这种硬件自动流控制手段。
-
[3:1]=000:规定必须为0
-
[0]=0:高电平(撤销nRTS),1:低电平(激活nRTS),因为,AFC位禁止,nRTS必须由软件控制。如果AFC使能,则忽略这个位。
-
*/
-
rUMCON0 = 0x0;
-
-
/*UBRDIV:波特率分频寄存器:用于设置波特率
-
[15:0],波特率分频值。使用UEXTCLK作为输入时钟时,可以设置UBRDIV为0.
-
UBRDIV = (int)(UART时钟/(波特率*16))-1
-
*/
-
rUBRDIV0 = UART_BRD;
-
}
-
-
/*功能:向串口发送一个字符
-
*参数:待发送的字符
-
-
UTRSTAT0:用来表明数据是否已经发送完毕,是否已经接受到数据
-
串口收发TX/RX状态寄存器,是CPU自动更新的,程序员检测它的状态就行。
-
[2]:发送移位寄存器空标志。
-
检测为0:表示发送移位寄存器非空,或发送缓冲寄存器非空。
-
检测为1:表示发送移位寄存器为空,且发送缓冲寄存器为空,程序员可以继续发送数据了。
-
[1]:发送缓冲区空标志位。
-
检测为0:表示发送缓冲寄存器非空,即上次要发送的数据还没有发完。不能太快,否则会覆盖掉。
-
检测为1:表示发送缓冲寄存器为空,程序员可以继续发送数据了。(非FIFO模式,非请求中断,非DMA)
-
注意:如果UART使用FIFO,用户应该使用UFSTAT寄存器中的Rx FIFO计数位 和 Rx FIFO满位取代对此位的检查。
-
[0]:接收缓冲器数据就绪标志位。
-
检测为0:表示接收缓冲器为空。
-
检测为1:表示接收缓冲器接收到有效数据,程序员可以取数据了。(非FIFO模式,非请求中断,非DMA)
-
注意:如果UART使用FIFO,用户应该使用UFSTAT寄存器中的Rx FIFO计数位 和 Rx FIFO满位取代对此位的检查。
-
-
UTXH0:串口发送缓冲寄存器
-
[7:0]:串口要发送的数据
-
*/
-
void uart_sendByte(int data)
-
{
-
if(data == '\n')
-
{
-
while(!(rUTRSTAT0 &0x2));
-
WrUTXH0('\r'); /*回车不换行*/
-
}
-
/*等待,直到发送缓冲区中的数据已经全部发送出去
-
这里也可以检测UTRSTAT0[2]位*/
-
while(!(rUTRSTAT0 &0x2));
-
/*向UTXH0寄存器中写入数据,UART即自动将它发送出去
-
#define rUTXH0 (*(volatile unsigned char *)0x50000020) //UART 0 Transmission Hold
-
#define WrUTXH0(ch) (*(volatile unsigned char *)0x50000020)=(unsigned char)(ch)
-
所以:WrUTXH0(data); 相当于 rUTXH0 = data;
-
*/
-
WrUTXH0(data);
-
}
-
-
/*功能:向串口打印一个字符串
-
*参数:待打印的字符串
-
*/
-
void uart_sendString(char *p)
-
{
-
while(*p)
-
uart_sendByte(*p++);
-
}
-
-
/*功能:向串口打印一个字符的16进制格式
-
*参数:待打印的字符
-
* 数字0=0x30,数字9=0x39
-
*/
-
void uart_sendByte_hex(unsigned long val)
-
{
-
// val = 0x1234ABCD
-
unsigned long c;
-
int i = 0;
-
-
uart_sendByte('0');
-
uart_sendByte('x');
-
-
for (i = 0; i < 8; i++)
-
{
-
c = (val >> ((7-i)*4)) & 0xf;
-
if((c >= 0) && (c <= 9))
-
{
-
c = '0' + c;
-
}
-
else if ((c >= 0xA) && (c <= 0xF))
-
{
-
c = 'A' + (c - 0xA);
-
}
-
uart_sendByte((int)c);
-
}
-
}
-
-
/*功能:从串口接受一个字符
-
*返回值:接受到的字符
-
-
UTRSTAT0: 串口收发TX/RX状态寄存器
-
[0]:接收缓冲器数据就绪标志位。
-
0:接收缓冲器为空
-
1:接收缓冲器接收到有效数据(非FIFO模式,非请求中断,非DMA)
-
注意:如果UART使用FIFO,用户应该使用UFSTAT寄存器中的Rx FIFO计数位 和 Rx FIFO满位取代对此位的检查。
-
-
URXH0:串口接受缓冲寄存器
-
[7:0]:串口接受到的数据
-
*/
-
char uart_getch(void)
-
{
-
/*等待,直到接受缓冲区中有数据*/
-
while(!(rUTRSTAT0 & 0x1));
-
/*直接读取URXH0寄存器,即可以获得接受到的数据
-
#define rURXH0 (*(volatile unsigned char *)0x50000024) //UART 0 Receive buffer
-
#define RdURXH0() (*(volatile unsigned char *)0x50000024)
-
所以:return RdURXH0(); 相当于:return rURXH0;
-
*/
-
return RdURXH0();
-
}
-
-
/*功能:从串口接受一个字符串
-
*参数:输入的字符串
-
*/
-
void uart_getString(char *string)
-
{
-
char *string2 = string;
-
char c;
-
while((c=uart_getch()) != '\r')
-
{
-
if(c == '\b')
-
{
-
if((int)string2 < (int)string)
-
{
-
//uart_printf("\b\b");
-
string--;
-
}
-
}
-
else
-
{
-
*string++ = c;
-
uart_sendByte(c);
-
}
-
}
-
*string = '\0';
-
uart_sendByte('\n');
-
}
-
-
-
/*******************************************************************************************
-
为了支持求余求模,需要:
-
1,修改makefile如下:增加libgcc的库
-
arm-linux-ld -Tuart.lds -o uart_elf $^ $(shell arm-linux-gcc -msoft-float -print-libgcc-file-name)
-
上面一句展开后,其实就是下面的一句:
-
## arm-linux-ld -Tuart.lds -o uart_elf head.o init.o uart.o leds.o /home/wangxc/linux/toolchain/gcc-3.4.5-glibc-2.3.6/bin/../lib/gcc/arm-linux/3.4.5/libgcc.a
-
2,自己手写libgcc的库。
-
这个在uart3实现
-
*******************************************************************************************/
-
-
/*功能:向串口格式化打印一个字符串
-
*参数:格式化的字符串
-
*注意:由于求模求余的问题没有解决,所以这里%d输出时,只能输出0~9,10和10以上的不能输出
-
*/
-
int uart_printf(const char *fmt, ...)
-
{
-
int count = 0;
-
char c;
-
char *s;
-
int n;
-
char buf[65];
-
va_list ap;
-
-
va_start(ap, fmt);
-
-
while(*fmt != '\0') //所有的'\n'都会输出
-
{
-
if(*fmt == '%')
-
{
-
fmt++;
-
switch(*fmt)
-
{
-
case 'd': /*整型*/
-
n = va_arg(ap, int);
-
if(n < 0)
-
{
-
//uputchar('-');
-
uart_sendByte('-');
-
n = -n;
-
}
-
itoa(n, buf);
-
//_uputs(buf);
-
uart_sendString(buf);
-
break;
-
case 'c': /*字符型*/
-
c = va_arg(ap, int);
-
uart_sendByte(c);
-
break;
-
case 'x': /*16进制*/
-
n = va_arg(ap, int);
-
//uart_sendByte_hex(n); /*由于求模求余编译有问题,所以用uart_sendByte_hex来替代下面的2行代码*/
-
xtoa(n, buf);
-
uart_sendString(buf);
-
break;
-
case 's': /*字符串*/
-
s = va_arg(ap, char *);
-
uart_sendString(s);
-
break;
-
case '%': /*输出%*/
-
uart_sendByte('%');
-
break;
-
default:
-
break;
-
}
-
}
-
else
-
{
-
uart_sendByte(*fmt); //最后一个 '\n'也会输出
-
if(*fmt == '\n')
-
{
-
//uart_sendByte('\r');
-
}
-
}
-
fmt++;
-
}
-
-
va_end(ap);
-
-
return count;
-
}
uart.h文件
-
#ifndef _UART_H_
-
#define _UART_H_
-
-
/**************************************************************************/
-
//串口相关寄存器
-
#define rULCON0 (*(volatile unsigned *)0x50000000)
-
#define rUCON0 (*(volatile unsigned *)0x50000004)
-
#define rUFCON0 (*(volatile unsigned *)0x50000008)
-
#define rUMCON0 (*(volatile unsigned *)0x5000000c)
-
#define rUTRSTAT0 (*(volatile unsigned *)0x50000010)
-
#define rUBRDIV0 (*(volatile unsigned *)0x50000028)
-
-
#define WrUTXH0(ch) (*(volatile unsigned char *)0x50000020)=(unsigned char)(ch)
-
#define RdURXH0() (*(volatile unsigned char *)0x50000024)
-
-
#define rGPHCON (*(volatile unsigned *)0x56000070)
-
#define rGPHUP (*(volatile unsigned *)0x56000078)
-
-
#define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
-
#define UART_CLK PCLK // UART0的时钟源设为PCLK
-
#define UART_BAUD_RATE 115200 // 波特率
-
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
-
-
void init_uart(void);
-
void uart_sendByte(int data);
-
void uart_sendString(char *p);
-
void uart_sendByte_hex(unsigned long val);
-
char uart_getch(void);
-
void uart_getString(char *string);
-
int uart_printf(const char *fmt, ...);
-
-
/**************************************************************************/
-
-
#endif
阅读(1128) | 评论(0) | 转发(0) |