摄像头驱动
一些概念:
摄像接口的主时钟信号由USB PLL产生,它的频率为96MHz,再经过分频处理后输出给摄像头,摄像头再根据该时钟信号产生三个同步时钟信号(像素时钟、帧同步时钟和行同步时钟),反过来再输入回s3c2440。
OV9650内部有大量的寄存器需要配置,这就需要另外的数据接口。
OV9650的数据接口称为SCCB(串行摄像控制总线),它由两条数据线组成:一个是用于传输时钟信号的SIO_C,另一个是用于传输数据信号的SIO_D。SCCB的传输协议与IIC的极其相似,只不过IIC在每传输完一个字节后,接收数据的一方要发送一位的确认数据,而SCCB一次要传输9位数据,前8位为有用数据,而第9位数据在写周期中是Don’t-Care位(即不必关心位),在读周期中是NA位。SCCB定义数据传输的基本单元为相(phase),即一个相传输一个字节数据。
SCCB只包括三种传输周期,即3相写传输周期(三个相依次为设备从地址,内存地址,所写数据),2相写传输周期(两个相依次为设备从地址,内存地址)和2相读传输周期(两个相依次为设备从地址,所读数据)。当需要写操作时,应用3相写传输周期,当需要读操作时,依次应用2相写传输周期和2相读传输周期。(这些读写和IIC一样~)
OV9650有两个只读寄存器——0x1C和0x1D,用于存放厂家ID,数据分别为0x7F和0xA2,我们可以通过读取它们来判断s3c2440是否连接了OV9650。当确认连接了OV9650后,我们就可以把VGA(640×480)模式下YUV彩色空间的配置数组写入OV9650内 (二维数组:第一个表示寄存器地址,第二个表示要写入的数据)
注意比较三个变量的含义:
Width = 640 //源宽度
PrDstWidth =480 //目标宽度
SrcWidth =640 - WinHorOffset*2; //偏移后宽度
驱动函数编写步骤:
外围基础准备硬件初始化
IIC初始化, LCD初始化, UPLL时钟初始化(96M, 0V9650系统时钟) ,
GPIO初始化(GPJ),硬件和软件复位摄像头(GPJ12为CAMERARESET,rCIGCTRL)
0V9650寄存器配置
A、读取OV9650厂商ID,验证是否工作
B、复位所有OV9650寄存器
C、配置OV9650寄存器(用规定的二维数组直接配)
摄像头接口初始化
摄像头接口的一些寄存器初始化:rCIGCTRL,rCIWDOFST rCISRCFMT
rCIPRTRGFMT,rCIPRTAREA
B、设置内存首地址为LCD缓存数组首地址 :rCIPRCLRSA1~4
C、计算水平和垂直缩放比率和位移量,以及主水平、垂直比率
放入以下三个寄存器中rCIPRSCPRERATIO,rCIPRSCPREDST rCIPRSCCTRL
中断函数开启和指向
控制台菜单的编写与实现
显示视频
rCIPRSCCTRL|=(1<<15); //预览缩放开启
rCIIMGCPT =(1<<31)|(1<<29); //预览缩放捕捉使能
截图,定格
rCIPRSCCTRL &=~(1<<15); //预览缩放关闭
rCIIMGCPT &=~((1<<31)|(1<<29)); //预览缩放捕捉不使能
视频定格后,LCD_BUFFER数组就是图像数据
放大,缩小
调节偏移量HOffset ,VOffset,每次改变后重新初始化摄像头接口
四个额外写出需注意的函数:
1、计算主突发长度和剩余突发长度 CalculateBurstSize
2、/计算预缩放比率及移位量 CalculatePrescalerRatioShift
3、中断函数(只清中断,不干别的) camera_interrupt
4、SCCB总线函数的读写(像IIC) Rd_SCCB,Wr_SCCB
网卡驱动
DM9000的一些基本概念:
DM9000对外来说只有两个端口——地址口和数据口,地址口用于输入内部寄存器的地址,而数据口则完成对某一寄存器的读写。DM9000的CMD引脚用来区分这两个端口,当CMD引脚为0时,DM9000的数据线上传输的是寄存器地址,当CMD引脚为1时,传输的是读写数据。
我们把DM9000的A8和A9接为高电平,把A4~A7接为低电平,并且把DM9000的AEN接到s3c2440的nGCS4引脚上,则DM9000的端口基址为0x20000300,如果再把DM9000的CMD引脚接到s3c2440的ADDR2引脚上0x20000304(怎么计算?)
查了一下书,是和存储控制器有关系,每个nGCSx对应128M地址空间,8个nGCSx对应1G地址。
nGCS4刚好对应0x20000000开始的地址,但为什么是0x20000300?
再加上IObase:300
如果将 DM9000的CMD引脚接到s3c2440的ADDR2,由于CMD引脚的高低电平决定地址口和数据口,那么,ADDR2为0时,访问的
就是地址口,所以地址口的起始地址为 ARRD2为0的情况,即0x20000000 ;ADDR2为1时,(LADDR3~LADDR0 = 0100)访问的就是数
据口,所以数据口的地址即 0x20000004。
如果要写入DM9000中的某个寄存器,则先把该寄存器的地址赋予DM_ADDR_PORT,然后再把要写入的数据赋予DM_DATA_PORT即可。读取DM9000中的某个寄存器也类似。
DM9000寄存器介绍在数据手册11页开始:
DM9000内部有0x3FF大小的SRAM用于接受和发送数据缓存。在发送或接收数据包之前,数据是暂存在这个SRAM中的。当需要连续发送或接收数据时,我们需要分别把DM9000寄存器MWCMD或MRCMD赋予数据端口,这样就指定了SRAM中的某个地址,并且在传输完一个数据后,指针会指向SRAM中的下一个地址,从而完成了连续访问数据的目的。
基于ARP协议的DM9000编写步骤:
初始化EINT7中断,设置中断函数入口
因为DM9000的数据中断引脚INT是连接到s3c2440的外部中断7引脚上的
编写好读和写DM9000寄存器函数,用其配置DM9000的寄存器
并使其用中断方式接收网卡数据,查询方式发送数据
(这步挺难的,很多寄存器要配置,对照着Datasheet和别人的程序)
注:DM9000内的寄存器的地址宏定义时,不用管基址,直接按OFFSET定义
编写好DM9000发送和接受函数(位宽为16)
分别把DM9000寄存器MWCMD或MRCMD赋予数据端口
接受数据时要注意,按照规定的格式来编写
定义一个符合ARP协议格式的数组,用于数据传输
将该数组作为发送函数形参发给PC机请求包
编写中断函数,清屏蔽
将接收函数中的数组内容,用串口打印出控制台
SD卡驱动
SD(全名为Secure Digital Memory Card,安全数码卡)
s3c2440集成了SD控制器,可以方便地读写SD、MMC和对SDIO进行操作。
可以用SDIO控制器来编,也可用SPI总线来编
SDIO的应用是未来嵌入式系统最重要的接口技术之一,会取代目前GPIO式的SPI接口
具体的CMD 命令和协议内容自己看回资料了。
我使用的SD卡为手机的内存卡(不是MMC),型号为SDHC_V20_CARD,支持 PLV2.0协议
主要讲 编写简单的SD读写步骤:
准备工作,编写要使用到一堆东西:
检查SDIO命令发送,接收是否结束函数Chk_CMD_End(int cmd, int be_resp)
使用到的 CMD 和ACMD 函数(这部分要配置寄存器,要查值,比较难,参考别人的吧)
编写一个要用的结构体SD_STRUCT,用于记录cCardType和iCardRCA等
SD卡识别模式:
初始化:时钟400K,Type B, clk enable,SD卡模式,FIFO reset
CMD 0 ——》reset指令
CMD 8——》工作电压范围,初始化SDHC卡,看是否支持PLV2.0,返回类型
CMD 55——》RCA为0x0,使用ACMD41前必须使用
ACMD 41——》识别卡能否在给定的VDD下工作
CMD 2——》查卡的CID 信息
CMD 3——》要求系统给SD卡发送一个新相对地址 RCA
改时钟频率:25M,准备进入transfer状态
CMD 7——》进入 transfer状态
ACMD 6——》设置总线宽度为 4bit
数据传输模式:
若需要擦除:
{
CMD 32——》start address
CMD 33——》end address
CMD 38 ——》erase
}注意:擦除后必须要进行复位,即重新初始化前面步骤,不然不能进行读写
单块写 或多块写
CMD 7——》transfer模式
单块读或多块读
拓展:
将摄像头捕捉的图像,写进SD卡中,然后再还原显示出来
注意一个点:
就是要将LCD_BUFFER[480][272]的二维数组转化为LCD_BUFFER_SD[480*272]一维
一个疑问?好像只需 51200就可将整幅图像都显示出来,而不需要480*272那么多
附加部分(有空研究):
文件系统的构建:
[page]
12、NORFLASH 操作
norflash为EN29LV160AB
对norflash的操作主要就是读、写、擦除和识别
EN29LV160AB的数据宽度可以是8位字节型,也可以是16位的字型,它由某一引脚配置实现的。在这里我们选择字型
BYTE#位置硬件接地,直接默认为Word Mode(16位)
写操作只能使“1”变为“0”,而只有擦除才能使“0”变为“1”。因此在写之前一定要先擦除
对norflash另一个比较常用的操作是读取芯片的ID
NorFlash启动,nGCS0接NorFlash,起始地址为0x0。0x0000 0000 –0x0800 0000为NorFlash
注意几个地方:
1、#define CMD_ADDR0 *((volatile U16 *)(0x555<<1+flash_base))
之所以又把norflash中的地址向左移一位(即乘以2),是因为我们是把s3c2440的ADDR1连接到了norflash的A0上的缘故,(自己看原理图),容易理解,等于本来最低位的数被推后没了,要左移拉回来。
2、check_toggle函数(校验函数)中
oldtoggle = *((volatile U16 *)0x0); newtoggle = *((volatile U16 *)0x0);
表示两次读值
if((oldtoggle & 0x40)==(newtoggle & 0x40)) //表示两次的DQ6是否相同
if(newtoggle & 0x20) //DQ5是否为1
几个常用的函数:
读函数: read_en29lv160ab(U32 addr)
return *((volatile U16 *)(addr));// 数据要转为16位
写函数: en29lv160ab_program(U32 addr, U16 dat)
第一个周期是把命令0xAA写入地址为0x555的命令寄存器中,
第二个周期是把命令0x55写入地址为0x2AA命令寄存器中,
第三个周期是把命令0xA0再写入地址为0x555命令寄存器中,
第四个周期为真正地把要写入的数据写入到norflash的地址中。
复位函数: reset_en29lv160ab(void)
是向任一地址写入复位命令0xF0
擦除函数: en29lv160ab_sector_erase(U32 section_addr)
第一个周期是把命令0xAA写入地址为0x555的命令寄存器中,
第二个周期是把命令0x55写入地址为0x2AA命令寄存器中,
第三个周期是把命令0x80再写入地址为0x555命令寄存器中,
第四个周期是把命令0xAA写入地址为0x555的命令寄存器中,
第五个周期是把命令0x55再写入地址为0x2AA命令寄存器中,
第六个周期是把命令0x30写入要擦除块的首地址内
验证函数: check_toggle()
连续两次读取数据总线上的数据,判断数据总线上的第6位数值(DQ6)是否翻转,如果没有翻转则正确,否则还要判断第5位(DQ5),以确定是否是因为超时而引起的翻转。
读ID函数: get_en29lv160ab_id()
第一个周期是把命令0xAA写入地址为0x555的命令寄存器中,
第二个周期是把命令0x55写入地址为0x2AA命令寄存器中,
第三个周期是把命令0x90再写入地址为0x555命令寄存器中,
第四个周期为读取地址为0x100中的内容,即厂商ID(0x1C)。
读取设备ID的过程的前三个周期与读取厂商ID相同,第四个周期是读取地址为0x01中的内容,即设备ID(0x2249)
问题1:
原来真的是写得块地址问题,写到前面的0x400就行,写后一点如0xf0000就读不出来了,0x0000 0000 –0x0800 0000为NorFlash,为什么后面的不行呢?
问题2:
本来擦除后写完能读出写的内容,但再复位后不写,只读回上次地址,发现读出不同的数据
而且好像擦除后就算返回return 1也没用,擦后读回那地址,也不是全部数据为0的,也是有值的
13、NANDFLASH操作
nandflash为K9F2G08U0A,它是8位的nandflash
一个劣势是很容易产生坏块,因此在使用nandflash时,往往要利用校验算法发现坏块并标注出来
K9F2G08U0A的一页为(2K+64)字节(加号前面的2K表示的是main区容量,加号后面的64表示的是spare区容量),它的一块为64页,而整个设备包括了2048个块
内存容量大小计算:
要实现用8个IO口来要访问这么大的容量,K9F2G08U0A规定了用5个周期来实现。
列地址是用于寻址页内空间,行地址用于寻址页,如果要直接访问块,则需要从地址A18开始。
CMD_STATUS读状态命令可以实现读取设备内的状态寄存器,通过该命令可以获知写操作或擦除操作是否完成(判断第6位),以及是否成功完成(判断第0位)。
自己编写NANDFLASH的步骤:(进行块擦除,页写,页读)
1、在头文件中列出相关的固量 和 函数 宏
常用的各种命令集;一些基于NANDFLASH控制器的基本函数
ECC应用和使能nandflash片选;NFSTAT寄存器
一些返回判断值的宏定义
2、写出几个基本的初始化函数:
管脚,寄存器初始化函数 ; 复位函数 ; 读NAND ID号函数
3、相关的检测判断函数:
A、检测是否写擦除成功函数NF_toggle:通过NF_CMD(CMD_STATUS)状态位
B、测试是否坏块函数NF_testBadBlock:读取2054处坏块标志位
C、写坏块标志函数NF_MarkBadBlock:写标志到 2054处
4、读、写、擦除函数:
具体自己看代码了,有两点要注意了:
A、读操作 检测正确性是通过ECC检验码,而写和擦除是通过toggle函数
B、随机读写 是 在读页,写页的基础上 加上列的部分
注意区别两个东西:
1、ECC检验码的判断
读操作来说,我们还要继续读取spare区的相应地址内容,已得到上次写操作时所存储的main区和spare区的ECC,并把这些数据分别放入NFMECCD0/1和NFSECCD的相应位置中。最后我们就可以通过读取NFESTAT0/1(因为K9F2G08U0A是8位IO口,因此这里只用到了NFESTAT0)中的低4位来判断读取的数据是否正确,其中第0位和第1位为main区指示错误,第2位和第3位为spare区指示错误。
2、坏块的判断
对于写页和擦除操作来说,通过toggle函数检测:
NF_CMD(CMD_STATUS); //读状态命令
//判断状态值的第6位是否为1,即是否在忙
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
若第0位操作错误,则该块为 坏块
3、main区和spare区的解锁只为了产生ECC码,一旦产生后,就要锁上,打开另外区的,不要影响互相顺序的ECC码产生
扩展:从NANDFLASH启动程序
当检测到是由nandflash启动时,系统会自动把nandflash中的前4k字节的数据加载到Steppingstone中,然后把该Steppingstone映射为Bank0,因此系统会从Steppingstone开始运行程序,从而实现了s3c2440的nandflash自启动的功能
通过在Steppingstone中,把程序的其余部分复制到RAM中,程序运行时由Steppingstone转移到该RAM中,SRAM起始地址0x30000000
RdNF2SDRAM是C语言编写的一段把nandflash中的数据复制到SRAM的程序
看看这函数,了解一下就行了
附加:nandflash、norflash、SDRAM的区别:
ROM在系统停止供电的时候仍然可以保持数据,
RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存。
SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲
动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。
[page]
NOR和NAND是现在市场上两种主要的非易失闪存技术。Intel于1988年首先开发出NOR flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面。
NOR比较好操作,因为集成了系统地址线和数据线在芯片内,而NAND只有8个I/O,要操作,还需要结合相应硬件,如2440里有的 NAND控制器。
14、基于OHCI的USB主机(仅做了枚举)
部分经典内容摘要:
在OHCI规范中,最重要的几个概念是端点(EndPoint - ED)、传输描述符(Transport Descriptor - TD)、主机控制器通信区(HCCA)。其中ED负责确定传输类型(控制传输、批量传输、同步传输和中断传输)。TD确定传输参数。HCCA用于确定数据传输是否完毕。
OHCI(基本流程)
进行控制/批量传输的主要处理流程如下:
1、创建控制/批量传输的ED列表;
2、创建ED下的TD列表;
3、设置命令到相应寄存器开始数据传输;
4、在中断处理程序中判断数据传输是否结束;
在OHCI层,主要完成如下功能:
l 通过控制端口读写数据(包含SETUP、DATA、STATUS等3个TD);
l 通过控制端口发送设置命令(没有DATA的TD);
l 通过批量端口读数据;
l 通过批量端口写数据;
l 中断处理程序;
在OHCI的体系下,判断数据是否传输完毕是需要通过中断程序来判断的,当USB主机设置了HcControl和HcCommandStatus寄存器开始传输数据后,AM9200 自动开始数据传输,并且定期的检查HcDoneHead寄存器的内容,并且将其转移到HCCA.DoneHead。然后产生中断,触发中断处理程序。
在中断处理程序中需要判断当前结束的TD是否是当前命令的最后一个TD,这样才能确保整个ED处理完毕。
进行U盘的数据传输时需要通过批量传输端口收发数据,所使用的协议为Mass Storage协议
要学习的一种数据结构与结构体结合应用的模式:
声明时:
typedef struct _ED {
…………
} ED, *P_ED;
__inline void CreateEd( unsigned int EDAddr , ………….)
{
P_ED pED = (P_ED) EDAddr; //这个在函数中创建结构体pED来初始化
pED->Control =…………………….
}
调用时,因16字节对齐:
__align(16) ED ed; (声明)
CreateEd(
(unsigned int) &ed, // ED Address
………….
);
简单的USB设备枚举,读取描述符:(控制传输步骤)
搞好最重要的3个数据结构:
端点描述符ED,传输描述符TD和共享数据域HCCA
由于HCCA只是创建一个空间域,不需要初始化
而ED和TD在设备枚举时需要不同初始化,故还要创建两个
初始化结构体函数:CreateEd 和CreateGenTd
初始化OCHI寄存器(仅限枚举部分,不涉及中断)
复位,设置帧间隔,初始化HcDoneHead,设置HC为运行状态
写HCCA(开拓一片域)
检测是否有USB设备
设一定的时间检测,如 for(i=0;i<100000;i++)
设备枚举的5个过程:第一步,主机得到设备描述符
第二步 为设备分配地址
第三步,主机用新的地址再次获取设备描述符
第四步,主机读取设备全部配置描述符
第五步,主机发送SETUP数据包,用以设置配置,允许所有端点进入工作状态。
注意:控制写传输需要3个TD:第一个发送Setup包,第二个用于接收握手或零长度的数据包,第三个用于发送状态;
控制读传输需要4个TD:第一个发送Setup包,第二个用于接收数据,第三个用于发送一个零长度的数据包,,第四个用于接收状态
具体有两种方法判断TD是否传送完成:
中断法
初始化好中断寄存器rHcInterruptEnable |= (1<<1)|(1<<31);
rHcInterruptStatus |= (1<<1);
pISR_USBH = (int)USBH_interrupt;
rINTMSK = ~BIT_USBH;
当有TD完成时,便进入中断。
如何判断枚举过程中每一步是否最后一个TD完成呢?
可以通过TD创造函数中DelayInterrupt即DI变量设置来巧妙解决
当DI=0x7时,即使TD完成也不会进入中断的,故可以只在每一阶段最后的TD设为非0x7,其余的设为0x7,这样进入中断就代表是最后数据发送完成了
2、状态检测法:
可以通过rHcCommandStatus寄存器中第二位是否1来判断Controllistfilled。
当其为1时,表示还有TD在队列,没发送完成
为 0时,表示无TD在队列,全部发送完
可以这样检测:while(rHcCommandStatus&&0x02)
{
Delay(50);
}
具体的UFI读写设备参考网上的吧(弄了很久,还没弄成功)
1、查询
2、读余量
3、读写扇区
好啦,到这里,算是结束了,开始带LINUX系统的真正学习啦,GO
一切都会有新的开始。。。。。。。。。。