哎,以前总想裸机加系统一齐学,学着学着发现好混乱,D驱动~~然后又重新斋跑2440的裸机。以下系少少心得,记录下学习过程。
里面有三个地方还没弄好:1、 硬盘的文件系统 FAT部分(后面有空补补)
2、 NORFLASH中的读写有点问题
3、 USB主机中对U盘的UFI命令
有什么问题欢迎一起交流。以下是部分心得:
LED
GPXCON中每两位控制一个引脚:00输入,01输出,10特殊功能
初始化 rGPBCON , rGPBUP , rGPBDAT 三个寄存器
关于ADS配置的解释:
在Target Settings中的Post-linker中选择ARM fromELF,因为我们需要把ELF文件格式转化为下载到flash中所必须的二进制文件。
在Language Settings中的所有语言的Target下的Architecture or Processor,选择ARM920T,因为s3c2440是ARM920T内核。
在ARM Linker中的Output中的RO Base填写0x30000000,因为在开发板上SDRAM是从0x30000000地址开始。在Layout中的Object/Symbol中填写2440init.o,它是启动文件的目标文件,在Section中填写Init,它是在启动文件中我们所定义的入口代码段的名称。
外部中断
我们要打开某一中断的屏蔽,这样才能响应该中断,相对应的寄存器为INTMSK;
还要设置外部中断的触发方式,如低电平、高电平、上升沿、下降沿等,相对应的寄存器为EXTINTn。
另外由于EINT4到EINT7共用一个中断向量,EINT8到EINT23也共用一个中断向量,而INTMSK只负责总的中断向量的屏蔽,要具体打开某一具体的中断屏蔽,还需要设置EINTMASK。
有一些中断是共用一个中断向量的,而一个中断向量只能有一个中断执行函数,因此具体是哪个外部中断,还需要EINTPEND或rINTOFFSET来判断
使用__irq这个关键词来定义中断处理函数,这样系统会为我们自动保存一些必要的变量,并能够在中断处理函数执行完后正确地返回
这种形式:void __irq key(); pISR_EINT0=(int)key;
定时器使用
主要配置 :外部时钟源→通过寄存器MPLLCON得到FCLK→再通过寄存器CLKDIVN得到HCLK和PCLK->再得到定时器的工作频率
PWM定时器使用:
蜂鸣器=GPB0=TOUT0=定时器0
蜂鸣器 高电平响,低电平不响
关键是设置寄存器TCNTBn和TCMPBn,前者可以确定一个计数周期的时间长度,而后者可以确定方波的占空比
定时器中断使用:
不需要配置TCMPBn,只需TCNTBn,TCNTBn*1/定时器的工作频率=定时的时间
但要记住中断要:
void __irq timer0_sever();
pISR_TIMER0=(int)timer0_sever;
rINTMSK =~BIT_TIMER0;
Uart
主要配置以下寄存器:
UBRDIVn ,UCONn ,UTRSTATn
收发寄存器: UTXHn ,URXHn
注意几点:
1. 对于s3c2440来说,接收数据是被动的,发送数据是主动的,因此一般来说,接收数据用中断方式,发送数据用查询方式较好;
2. 在中断方式下,当接收到数据时,尽管可能该数据无用,但也一定要读取它,否则下次再接收数据时,不会再引起中断,因为接收数据缓存器被上次接收到的数据所霸占,只要没有读取它,它就永远在那里;
3. 由于UART中断涉及到SUBSRCPND寄存器,因此在中断处理程序中不仅要清SRCPND寄存器,还要清SUBSRCPND寄存器,它们的顺序一定是先清SUBSRCPND寄存器,再清SRCPND寄存器,否则就会引起一个中断两次响应的问题。因为是否中断由SRCPND寄存器决定,而SRCPND寄存器的相关状态位由SUBSRCPND寄存器决定
简单的串口控制台:1. 键盘输入数据后在控制台上进行回显
recv_data = uart_getc();
return recv_data;
2. 让接收的字符组成命令 data_buf[i] = uart0_scanf();
if(data_buf[i] == '\0')
3回车——命令判断、退格——删除字符、判断命令后进入相应分支进行处理
else if(data_buf[i] == '\b')
if(!(no_system_strcmp("read iic",cmd_buf)))
LCD (4.3寸,480*272)
在2440中时钟设置是 FCLK:HCLK:PCLK=400:100:50
注意两点:
1时序,参数的设置
VCLK是像素时钟信号。VCLK=HCLK÷[(CLKVAL+1)×2]
例如,HCLK的频率为100MHz,要想驱动像素时钟信号为6.4MHz的LCD屏,则通过上式计算CLKVAL值
在s3c2440中,所有的这些信号(VSPW、VFPD、VBPD、LINEVAL、HBPD、HFPD、HSPW和HOZVAL)都是实际值减1的结果。这些值是通过寄存器LCDCON2、LCDCON3和LCDCON4来配置
行频(HSF)和场频(VSF):
HSF=VCLK÷[(HSPW+1)+(HSPD+1)+(HFPD+1)+(HOZVAL+1)]
=7.1÷408=17.5kHz
VSF=HSF÷[(VSPW+1)+(VBPD+1)+(VFPD+1)+(LINEVAL+1)]
=17.5÷270=64.8Hz
2、显示缓存区
一般我们是通过定义一个与屏幕尺寸大小相同的二维数组来开辟该空间的,这样控制屏幕内容会方便一些,如当屏幕的尺寸为480×272时,可以定义该缓存区为LCD_BUFFER[272][480]。
在s3c2440中,寄存器LCDSADDR1和LCDSADDR2用于设置显示缓存区,即把我们定义的那个二维数组告诉s3c2440。其中LCDBANK的9位数据指定LCD的BANK,即显示缓存区的第30位到第22位地址;LCDBASEU的21位数据指定了LCD的基址,即显示缓存区开始地址的第21位到第1位;LCDBASEL的21位数据指定了LCD的尾址,即显示缓存区结束地址的第21位到第1位。
拓展:LCD的字符显示
汉字库
中文字符和ASCII码混合在一样,如何区分它们呢?其实也很简单,ASCII码的最高位是0,而中文的最高位是1,因此当读取到的一个字节的最高位是0,则该字节为ASCII码,它的下一个字节与这个字节无关;当取得到的字节的最高位是1,则表示的是中文字符,并且该字节与它的下一个字节组合在一起表示完整的一个汉字。
把字库变换成一个超大的数组,那么我们就可以像操作数组一样读取字库了
#include "font_libs.h" //内有两个数组__HZK[ ]和__ASCII[ ]
if(String[k]&0x80) //中文字符
qh=String[k]-0xa0; //区号
wh=String[k+1]-0xa0; //位号
mould = & __HZK[ ( ( qh - 1 )*94 + wh- 1 )*32 ];
RTC实时时钟
内部的寄存器BCDSEC,BCDMIN,BCDHOUR,BCDDAY,BCDDATE,BCDMON和BCDYEAR分别存储了当前的秒,分,小时,星期,日,月和年,表示时间的数值都是BCD码。这些寄存器的内容可读可写,并且只有在寄存器RTCCON的第0位为1时才能进行读写操作。
“一秒误差”,因此当读取到的秒为0时,需要重新再读取一遍这些寄存器的内容,才能保证时间的正确。
以下两个中断实用,注意要按照一般中断初始化的格式:开屏蔽,清屏蔽
时间节拍中断 pISR_TICK时间节拍中断的周期公式为:(n+1)÷128,单位是秒
其中n的值为1~127,它存储在寄存器TICNT的低6位中,当寄存器TICNT的第7位被置1时,表示开启时间节拍中断
报警中断 pISR_RTC LMYEAR(年)、ALMMON(月)、ALMDATE(日)、ALMHOUR(小时)、ALMMIN(分)和ALMSEC(秒)。而如何报警,是由报警控制寄存器RTCALM控制的
要学习一下Fzb版本的控制台设计,其中提到一个专用函数
// 将一个十位数转化为字符串输出(专用函数) BCD码转字符串
static void digit_to_char(char *buf, int number)
{
buf[0] = ((char)(number >> 4) + 48); //转换高4位
buf[1] = ((char)(number & 0x0f) + 48); //转换低4位
}
要多学习用结构体,数组,和指针
IIC总线(按中断法写的,和51时按时序法不同)
SDA->GPE15 , SCL->GPE14
寻址AT24C02A
S3C2440作为主机,所以就关注主机模式下的收/发
配置了IIC管脚所在的寄存器,然后配置了IIC工作的信号以及时钟源的配置。配置rIICCON,rIICSTAT
设立变量FLAG,来看是否进入了中断,出来中断后要清除
while(FLAG == 1)
for(i = 0;i < 100;i++);
FLAG = 1;
读函数的流程: 首先是发出从设备寻找地址并指定为写入操作→写入要读取数据的首地址→发出从设备寻找地址并指定为读取操作→这时会先返回一个数据(这个是不需要的要抛弃,上面红色部分)→按照给定的数据长度读取数据(长度超过8位会折返读)→结束操作。
写函数的流程:发出从设备寻址地址并指定写入模式→向从设备写入要写入数据区的首地址→写入数据到设备中(最多8个字节)→结束。
几句经典的解析:
rIICCON = 0xaf; //清中断标记,开启中断
rIICSTAT = 0xf0; rIICSTAT = 0xb0; //使能接收,发送功能,S信号开始
rIICSTAT = 0xd0; rIICSTAT = 0x90; //发出P信号,停止
参考fzb的读写8位对齐法,很美观牛B
ADC和触摸屏
AD(轮询或中断 自己选择)
A/D转换比较简单,主要应用的是ADC控制寄存器ADCCON和ADC转换数据寄存器ADCDAT0
这个想法不错:在AIN2引脚上接了一个温度传感器,被检测的温度范围为0度~99度,它对应于A/D转换数据的0~0x3FF。检测到的温度被实时地显示在LCD上,这里我们只显示温度的整数部分。
temperature = (int)readADC()*99/0x3ff; //读取温度?可以直接这样?不用写时序让温度传感器工作先?这种不是DS18B20,是那种直接纯传感
注意的地方:
根据S3C2440的中断体系,从INTOFFSET获取中断发生的种类标号然后就像这样处理各个中断。ADC的中断体系分为两个层次。首先是主要中断,由于ADC和触摸屏使用同一个主要中断标号,所以在这个中断处理中还需要判断次要中断以最终确定到底是ADC还是触摸屏发生了中断。
触摸屏
工作的两种典型模式:
自动(序列)X/Y坐标转换模式
自动(序列)X/Y坐标转换模式作为如下方式被操作。触摸屏控制器依次转换被触摸点的X坐标和Y坐标。在触摸控制器写X测量数据到ADCDAT0和Y测量数据到ADCDAT1过后,触摸屏接口产生中断到中断控制器在自动坐标转换模式中。
等待中断模式
触摸屏控制器产生中断信号(INT_TC)当触摸笔按下的时候。等待中断模式设置值是ADCTSC = 0xd3(X_PU、XP_Dis、XM_Dis、YP_Dis、YM_En)。
一般来说,触摸屏的响应过程是:等待中断(触摸笔按下)模式→中断发生后进入自动(连续)X/Y轴转换→等待中断(触摸笔抬起)模式。
这三种模式的初始化分别为:
1等待中断(触摸笔按下):
rADCTSC=0;
rADCTSC=0xd3;
2(连续)X/Y轴转换:
rADCTSC=0;
rADCTSC=0x04;
3等待中断(触摸笔抬起):
rADCTSC=0;
rADCTSC=0xd3;
rADCTSC |=(1<<8);
晕,看回以前的写字板程序,都是错,没有区分清楚两个不同的中断
只有一个中断触发函数pISR_ADC 但是对应着不同的两个中断,
要通过在中断中判断if(SUBSRCPND & (1 << 9)) 和else if(SUBSRCPND & (1 << 10))
等待检测 AD处理中断
注意: 在等待检测中,又分为 按下中断 和 抬起中断
用 if(ADCDAT0 & 0x8000) 和 else 来区分
抬起 按下
拖曳功能的实现:
Y
按下——》xy自动模式转换——》收集数据,画点——》检测是否松手
N
X通过 标志 flagTC 来判断是否松手,若松手,flagTC在松手中断中置1
上面那种标志的方法会与 void TC_interrupt()//等待模式中断,判断是按下还是松手
有矛盾
故还是直接采取在拖曳函数中test_down_still()直接判断
wait_up_init();
if(!(rADCDAT0&0x8000))
每次画点要延时一段时间,即控制好采样时间
校正:
利用三个坐标点 通过公式计算出待定系数A,B,C,D,E,F,K ,表示出触摸屏和LCD关系
公式:LCD上每个点PD的坐标为[XD,YD],触摸屏上每个点PT的坐标为[XT,YT]。
XD=A×XT+B×YT+C
YD=D×XT+E×YT+F
K=(XT0-XT2)×(YT1-YT2)-(XT1-XT2)×(YT0-YT2)
A=[(XD0-XD2)×(YT1-YT2)-(XD1-XD2)×(YT0-YT2)] / K
B=[(XT0-XT2)×(XD1-XD2)-(XD0-XD2)×(XT1-XT2)] / K
C=[YT0×(XT2×XD1-XT1×XD2)+YT1×(XT0×XD2-XT2×XD0)+YT2×(XT1×XD0-XT0×XD1)] / K
D=[(YD0-YD2)×(YT1-YT2)-(YD1-YD2)×(YT0-YT2)] / K
E=[(XT0-XT2)×(YD1-YD2)-(YD0-YD2)×(XT1-XT2)] / K
F=[YT0×(XT2×YD1-XT1×YD2)+YT1×(XT0×YD2-XT2×YD0)+YT2×(XT1×YD0-XT0×YD1)] / K
利用EEPROM来保存这几个参数,即A,B,C,D,E,F,K分别保存在以0x20,0x30,0x40,0x50,0x60,0x70,0x80为首地址内存的连续4个字节空间内,另外内存地址0x1F保存一个标识信息,当为0x6A时,表示这几个参数已计算并保存好了,只需从上述内存地址中读取参数就行,而当为其他值时,就需要进行校正。
难点是如何将其写存入 E2PROM,不用每次都重新校正:
注意两个函数:因为E2PROM的每个地址单位是以1字节(8位的)
//把一个32位整型转换为4个8位字节型,并写入EEPROM中
iic_buffer[0]=(unsigned char)((coef&0xFF000000)>>24);
iic_buffer[1]=(unsigned char)((coef&0x00FF0000)>>16);
iic_buffer[2]=(unsigned char)((coef&0x0000FF00)>>8);
iic_buffer[3]=(unsigned char)(coef&0x000000FF);
//读取EEPROM中的4个8位字节,并把它们组合成一个32位的整型。
temp=(iic_buffer[0]<<24)|(iic_buffer[1]<<16)|(iic_buffer[2]<<8)|(iic_buffer[3]);
感觉都不知道否IIC读写函数有问题?怎么下面一个读函数读不出值?
rdat24c02a(0x1F,iic_buffer,1); 读0x1f的位置的值
if(iic_buffer[0]!=0x6A) 判断是否值等,即校正过的
9、IIS音频
一些基本概念:
扩展了IIS的应用,要外扩CODEC才行。因为接口的数据传出和接收的只是数字采样信号而已,要通过CODEC进行DA转换或者AD转换才行。
一个wav文件作为例子(采样频率44.1KHz、声道数2、数据位数16)。该文件的声音要得到还原,那么数据必须以44.1KHz的速率传输
IISSCLK=采样频率×采样位数×通道数,采样频率记为fs。于是IISSCLK为32fs
轮询FIFO法的使用:
寄存器配置好预分频器和音频数据的数据位、主时钟、数据时钟以及FIFO后便可以读或者写FIFO来实现上述功能
UDA1341TS的配置,只有配置了它(CODEC)模拟信号才能正确的转换为IIS接口的数据或者将IIS接口的数据转换为模拟信号输出。这部分的配置是通过一个叫做“L3”总线接口来实现
UDA1341TS的L3微控制器部分说明(当AD和DA用)
L3写的一个实现,通过这个我们可以向CODEC发出各种命令进行配置,由于TQ2440将这三个管脚和GPIO相连,所以我们按照严格的地址模式和数据传输模式时序来编写总线操作函数
注意小技巧,宏定义:#define L3M (1<<2) //GPB2 L3MODE
#define L3D (1<<3) //GPB3 L3DATA
#define L3C (1<<4) //GPB4 L3CLOCK
地址模式 UDA1341TS的地址是000101。
数据位0到1指向随后传送数据的类型
数据传输模式 各总线数据的赋值,就像赋给寄存器一样
轮询FIFO法具体使用步骤:
1、 初始化底层硬件 :配置rGPBCON,rGPECON,设波特和主频:rMPLLCON和rUBRDIV0
2、配置UDA1341 :STATUS,DATA0和扩展地址
3、配置s3c2440的IIS寄存器 :rIISPSR, rIISCON,rIISMOD, rIISFCON
4、输入或输出数据 : 注意FIFO中的数据为16位,深度为32
if((rIISCON & (1<<6))==0) //检查输入FIFO是否为满
temp=(*IISFIFO); //录音读取
(*IISFIFO)=music; //放音写入
5、结束恢复 :rGPBCON,rGPECON,设波特和主频:rMPLLCON和rUBRDIV0
DMA通道法的使用:
DMA的一些基本概念:
DMA的优势是它在传输数据的时候不需要CPU的干预。
DMA一大堆寄存器要配置:
思路:描述源的地址和控制方式;描述目地的地址和控制方式;描述DMA控制方式;打开DMA操作。
难度最大的基本也就是DMA的控制方式配置。DMA控制方式的设置主要是这么几个方面:选择DMA传输控制方式;选择DMA是否开启中断;选择DMA服务的对象;DMA服务方式。
DMA通道法的使用步骤:(基本和轮询法差不多,再补一些不同的)
1、初始化底层硬件 +中断部分初始化
2、配置UDA1341
3、配置s3c2440的IIS寄存器 将FIFO改为DMA模式
4、配置DMA中寄存器 (输出要用DMA通道2,输入用通道1)
5、中断处理 利用商和余数,判断每次传0xfffff*2后还剩多少数据量
6、结束还原,关DMA,关中断
几个要注意的小地方:
每次中断的最大传输量:字节大小为:DSZ×TSZ×TC= 半字 * 单元传输 * 寄存器DCONn的低20位存放的数据=(0xfffff<<1)= 2 * 1 * DCONn中的值
MP3模式处理函数(静音,音量增减,停止,暂停、开始)
用FZB的串口控制台命令来编写
//输入以下东西实现功能:
//1、"mute" 静音 在DATA0中MT位置置一
//2、"++""--" 音量增减 通过对volume变量赋值,然后通过L3写进(0x14,1)中
//3、"stop" 停止 关DMA2,即关rDMASKTRIG中的stop位
//4、"pause" 暂停/开始 关开 rIISCON
DMA的播放/录音实现和轮询实现的区别
在于,轮询需要自己编写数据输出/读取代码,但是DMA方式下不用,只需要指明源地址和目标地址以及数据大小就可以由硬件自动传输数据了,只是要注意的是S3C2440下的DMA使用时候数据大小是有限制的,一次最多可以传输的字节大小为:DSZ×TSZ×TC
由于是用DMA的方式,因此在播放的过程中不占用系统资源,我们可以很容易的实现声音的各种操作而丝毫不影响播放的效果,如音量的提高和降低、静音、暂停等