http://www.linuxidc.com/Linux/2011-06/37583.htm
快要毕业了,毕业之前再重新把这些实验从头到尾的练习一遍,先开始裸机的吧,这些都是以前搞过的东西,现在应该会比较快。。
开始吧!
时钟对于电子设备来说都是非常重要的,它是传输数据的一个基准,如果没有这个基准的话将导致系统的混乱。
S3C2440的频率有两种输入方式:外部时钟源和内部晶振(如下图)
输入的频率一般是比较低的比如2440的就只有12M,而2440的主频可以达到460M,这就需要对输入频率通过PLL锁相环进行倍频
先来看下这个CLOCK的结果图:
从上面的结果图可以看出输入频率OSC首先经过MPLL倍频
整个系统时钟主要有几个组成:FCLK,HCLK,PCLK
FCLK:是个cpu提供时钟
HCLK:用于AHB总线,中断控制器,LCD控制器,内存控制器提供时钟
PCLK:用于APB总线,通常给IIC,WDT,IIS,ADC, UART, GPIO, RTC and SPI.等外设提供时钟
下面是整个时钟系统的几种工作方式:正常,空闲,慢,睡眠模式。
慢模式:也就是没有通过MPLL倍频,直接就由外部时钟源或者内部晶振来提供时钟,所以系统的功耗有时钟源来决定
设置系统时钟主要配置几个寄存器:
MPLLCON:设置P,S,M的值
CLKDIVN:设置FCLK,HCLK,PCLK的比例关系
MPLL和UPLL的计算公式不同:
Mpll = (2*m * Fin) / (p * 2s)
m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2,s = SDIV
UPLL Control Register
Upll = (m * Fin) / (p * 2S)
m = (MDIV + 8), p = (PDIV + 2), s = SDIV
另外需要特别注意的一点,如果直接采用上面的式子计算输出频率很可能出错,因为会发生溢出,故采用下面的式子:
FOUT = 2 * m * (Fin/100) / (p*2S)×100,
下面是核心代码:
1.//为了使FCLK=400Mhz,我们取m=92,p=1,s=1.也可以根据手册p255频率表中的数值,
2. //但是没有400Mhz的组合。可以选接近的
2.rMPLLCON = 92<<12 | 1<<4 |1 ;
3.rCLKDIVN = 2<<1 | 1; //FCLK:HCLK:PCLK = 1:4:8 4.rCAMDIVN = 0<<9 ; //配置CLKDIVN[2:1]与改位有关,可参考手册说明 5.m = (rMPLLCON>>12) &0xff ;
6.p = (rMPLLCON>>4) & 0x3f;
7.s = rMPLLCON & 0x3;
8.FCLK = ((m+8) * 2 * (FIN/100)) / ( (p+2) * (1 << s))*100;
9.PCLK = FCLK >>3;
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583.htm
调试程序时,我们可以使用查看变量的方法,但是还是不能离开调试信息的打印。。。。。在开始其他实验时,首先把uart功能实验,方便调试。
1.UART 支持中断模式和DMA模式
2.如果是使用系统时钟,UART最高能支持115.2k/s的数据传输,如果是使用外部的时钟,速度将更高,每道uart有FIFO模式和非FIFO模式,在FIFO模式中有两个64字节的FIFO分别用于接收和发送数据,在非FIFO模式中只有1字节的缓存区
3.下面是uart的结果图
4..从上面的结构图可以看出,UART由:波特率发生器,发送缓存和接收缓存,控制单元四个部分组成,波特率发生器可以是系统时钟(PCLK,FCLK/N)或者外部时钟(UEXTCLK),发送器和接收器各包含一个64字节的FIFO和数据移位器。要发送数据时,先将数据写入到FIFO接着在发送前复制到发送移位器中,随后将数据从发送数据引脚(TXDn)移出;接收数据时,从接收数据引脚(RXDn)移入收到的数据,接着从移位器复制到FIFO。
5.
在串行通讯处理中,常常看到硬件流控制(RTS/CTS)和软件流控制(XON/XOFF)这两个选项,这就是两个流控制的选项,目前流控制主要应用于调制解调器的数据通讯中
硬件流控制常用的有RTS/CTS流控制和DTR/DSR(数据终端就绪/数据设置就绪)流控制。
由于电缆线的限制,我们在普通的控制通讯中一般不用硬件流控制,而用软件流控制。一般通过XON/XOFF来实现软件流控制。常用方法是:当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发出XOFF字符(十进制的19或Control-S,设备编程说明书应该有详细阐述),发送端收到XOFF字符后就立即停止发送数据;当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发出XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。一般可以从设备配套源程序中找到发送的是什么字符。
应该注意,若传输的是二进制数据,标志字符也有可能在数据流中出现而引起误操作,这是软件流控制的缺陷,而硬件流控制不会有这个问题。
在2440中,只有当CTS信号有效才可以发送数据,CTS有效表明其他的UART的FIFO准备好接收数据,在接收数据之前,RTS应该是有效的,并且FIFO应该是大于32字节,否则为非有效状态
注意:2440的UART 2不支持自动流控制(AFC)
6.LoopBack操作模式:
S3C2410 CPU的UART提供了一种测试模式,也就是这里所说的LoopBack模式。在设计系统的具体应用时,为了判断通讯故障是由于外部的数据链路上的问题,还是CPU内驱动程序或CPU本身的问题,这就需要采用LoopBack模式来进行测试。在LoopBack模式中,资料发送端TXD在UART内部就从逻辑上与接收端RXD连在一起,并可以来验证资料的收发是否正常。
7.uart波特率计算公式:
UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1
For example, if the baud-rate is 115200 bps and UART clock is 40 MHz, UBRDIVn is:
UBRDIVn = (int)(40000000 / (115200 x 16) ) -1
= (int)(21.7) -1 [round to the nearest whole number]
= 22 -1 = 21
8.核心代码:
1.#include "uart.h"
2.#include "2440addr.h"
3.#include
4.#include
5.#include
6.#include
7.#include
8.#include
9.
10.//端口和波特率初始化 11.void uart_init(int baud) 12.{
13.
14. int i; 15. //*** PORT H GROUP 16. //Ports : GPH10 GPH9 GPH8 GPH7 GPH6 GPH5 GPH4 GPH3 GPH2 GPH1 GPH0 17. //Signal : CLKOUT1 CLKOUT0 UCLK nCTS1 nRTS1 RXD1 TXD1 RXD0 TXD0 nRTS0 nCTS0 18. //Binary : 00 , 00 00 , 00 00 , 00 00 , 10 10 , 10 10 19. rGPHCON = 0xaa ;
20. //rGPHCON = 0x2a0aaa ; 21. rGPHUP = 0x7ff; // The pull up function is disabled GPH[10:0] 22. // 0 0 000 0 11 23. rULCON0 = 0x3;//Line control register : Normal,No parity,1 stop,8 bits 24. // [15:12] [11:10] [9] [8] [7] [6] [5] [4] [3:2] [1:0] 25. // 00 1 0 0 1 0 0 01 01 26.
27. rUCON0 =0x245;
28. rUFCON0 = 0x0; //不使用FIFO 29. rUMCON0 = 0x00; // 不使用流控 30. rUBRDIV0=( (int)(PCLK/16./baud+0.5) -1 ); //Baud rate divisior register 0 31. for(i=0;i<100;i++); 32.
33.
34.
35.}
36.
37.//字节发送 38.void uart_send_byte(int data) 39.{
40. if(data == '\n') { //结束符 41. while(!(rUTRSTAT0 &0x2)) ; //等待发送缓冲区为空 42. WrUTXH0('\r'); //直接写数据到UTXH0寄存器中 43. }
44. //特别需要注意下面的语句不是用else和if并列 45. while(!(rUTRSTAT0 &0x2)) ; //等待发送缓冲区为空 46. WrUTXH0(data);
47.
48.}
49.
50.//发送字符串 51.void uart_send_string(char *string) 52.{
53. while(*string) 54. uart_send_byte(*string++); 55.}
56.
57.
58.//实现类似printf函数变量输出 59.void uart_printf(char *fmt,...) 60.{
61. va_list ap;
62. char string[256]; 63.
64. va_start(ap,fmt);
65. vsprintf(string,fmt,ap); 66. uart_send_string(string); 67. va_end(ap);
68.}
69.
70.//从终端读取单个字符 71.char uart_getch(void) 72.{
73. while(!(rUTRSTAT0 &0x1)) ; //等待接收缓冲区中不为空 74. return RdURXH0(); //直接返回接收数据寄存器URXH0中的值 75.}
76.
77.//获取数据 78.char uart_get_key(void) 79.{
80. if(rUTRSTAT0 &0x1) //检查buffer中是否有数据 81. return RdURXH0(); 82. else 83. return 0; 84.}
85.
86.
87.//从终端得到一个字符串,保存到string中 88.void uart_get_string(char *string) 89.{
90. char *string2 = string; 91. char c; 92. while((c = uart_get_key())!='\r') //回车 93. {
94. if(c == '\b') { //backspace 95. if((int)string2 < (int)string) { 96. uart_printf("\b \b"); //删除最后一个字符 97. string --; 98. }
99. } else { 100. *string++ = c; 101. uart_send_byte(c); //将输入的字符回显到终端,如果没有该语句在终端上看不到输入的内容 102. }
103. }
104. *string = '\0'; 105. uart_send_byte('\n'); 106.}
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583p2.htm
看门狗实质上和其他的定时器功能相似,只是看门狗多了一个时间到就复位的功能,只要将WTCON的【0】设置成0就成了普通的中断,将进入中断处理程序中,如果设置成了1将复位。这样的话,我们可以使用看门狗来实现对程序的监视,定时的去喂狗也就是定时去给WTCNT复制,如果程序跑飞了话就不能定时去喂狗,系统将重启。
下面的程序中我使用了一个中断处理函数,中断处理程序中使蜂窝器响一下,如果开启了喂狗程序的话不会引发中断,
下面是测试程序:(特别注意延时的时间,否则不能正常工作)
1.#include "watchdog.h"
2.#include "def.h"
3.#include "2440addr.h"
4.#include "uart.h"
5.
6.void __irq watchdog_isr(void) 7.{
8.
9. uart_printf("Enter interrupt!\n"); 10. Beep(2000, 100);
11. //清中断标志位 12. rSRCPND = 0x1<<9;
13. rSUBSRCPND = 0x1<<13;
14. rINTPND = 0x1<<9;
15.
16.}
17.
18.
19.
20.void watchdog_test() 21.{
22.
23. // freq=PCLK / (Prescaler value + 1) / Division_factor 24. ////prescaler =249 division factor = 16 freq = 12.5kHZ 如果[0]为1,但中断发生时将引起复位, 25. //这里设置成了普通的中断,中断发生时会进入中断处理程序 26. rWTCON = (249<<8) | (0<<3) |(0); // 27. rWTDAT = 50000; //看门狗计数4秒(4/(1/12500) 28. rWTCNT = 50000; //这两个值必须初始化 29.
30. rWTCON |=(1<<5) |(1<<2); //开看门狗中断 31.
32. rSRCPND = 1<<9;
33. rSUBSRCPND =1<<13;
34. rINTPND =1<<9;
35. rINTSUBMSK = ~(1<<13); //开中断子屏蔽 36. rINTMSK =~(1<<9);
37.
38. pISR_WDT_AC97 = (U32)watchdog_isr; //安装中断处理句柄 39.
40. Delay(1000);
41.
42. while(1) 43.
44. {
45.
46. Delay(1000);
47.
48. // rWTCNT = 50000; //喂狗,重新赋值,防止中断,果 49. //把这条语句打开的话,将进行喂狗,不会进入中断 50.
51. Delay(1000);
52.
53. }
54.
55.}
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583p3.htm
花了两天的时间终于把这个搞定了,其实I2C的原理还是比较简单的,只是几个细节性的东西还是需要特别的注意,主要是需要注意一下几点:
1.rIICCON &= ~0x10; 清中断必须要在rIICDS = slvAddr; 和rIICSTAT = 0xf0; // 主设备,启动 之后
2.延时对于写外部的低速设备来说非常重要,比如while(flag)之后一定要加延时,还有在写数据时发现只能写入基数地址的数据,这也是由于延时导致的
3.开始调试的时候系统总是死在read的函数中,后来发现在数据手册的note中说当读取最后一个数据的时候一定不能返回ACK信号,而我却在程序中使用while(flag)来等待ACK引发中断,这不死才怪呢。。。。所以数据手册中的NOTE部分也是特别重要的
4.在真正对AT24C02A进行读取数据时,在发送带有读命令的从设备地址后,AT24C02A会再返回一个从设备地址信息或从设备内存地址信息作为应答,所以一定要把该字节读取后抛弃,因为它不是我们所要读取的信息。
5.下面是核心代码:
1.#include "def.h"
2.#include "2440addr.h"
3.#include "I2C.h"
4.#include "uart.h"
5.extern void Delay(int time); 6.int flag; //用于标识是否收到应答信号,改标识在终端处理程序中被清0 7.void Test_Iic(void) 8.{
9. unsigned int i,j,save_E,save_PE; 10. static U8 data[256]; 11. uart_printf("\nIIC Test(Interrupt) using AT24C02\n"); 12. save_E = rGPECON;
13. save_PE = rGPEUP;
14. rGPEUP |= 0xc000; //Pull-up disable 15. rGPECON |= 0xa00000; //GPE15:IICSDA , GPE14:IICSCL 16. pISR_IIC = (unsigned)IicInt;
17. rINTMSK &= ~(BIT_IIC);
18. //Enable ACK, Prescaler IICCLK=PCLK/16, Enable interrupt, Transmit clock value Tx clock=IICCLK/16 19. // If PCLK 50.7MHz, IICCLK = 3.17MHz, Tx Clock = 0.198MHz 20. rIICCON = (1<<7) | (0<<6) | (1<<5) | (0xf);
21. rIICADD = 0x10; //2440 slave address = [7:1] 22. rIICSTAT = 0x10; //IIC bus data output enable(Rx/Tx) 23. rIICLC = (1<<2)|(1); // Filter enable, 15 clocks SDA output delay added by junon 24.
25. uart_printf("Write test data into AT24C02\n"); 26. for(i=0;i<256;i++) 27. { Wr24C080(0xa0,(U8)i,i);
28. Delay(1); //注意这个延时不能少,否则出现有些数据无法写入的问题 29. }
30. for(i=0;i<256;i++) 31. data[0] = 0;
32. uart_printf("Read test data from AT24C02\n"); 33.
34. for(i=0;i<256;i++) 35. Rd24C080(0xa0,(U8)i,&(data[i]));
36. for(i=0;i<16;i++) 37. {
38. for(j=0;j<16;j++) 39. uart_printf("%2x ",data[i*16+j]); 40. uart_printf("\n"); 41. }
42. rINTMSK |= BIT_IIC;
43. rGPEUP = save_PE;
44. rGPECON = save_E;
45.}
46.void Wr24C080(U32 slvAddr, U32 addr, U8 data) 47.{
48. flag=1; //应答标志 49. rIICDS = slvAddr;
50. rIICSTAT = 0xf0; // 主设备,启动 51. rIICCON &= ~0x10; //清中断标志 ,特别注意这条语句的位置,不能放到上条的前面 52. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 53. Delay(1);
54.
55.
56. flag =1 ; //readly to translate addr 57. rIICDS = addr;
58. rIICCON &= ~0x10; //清中断标志 59. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 60. Delay(1);
61.
62. flag =1 ; //readly to translate data 63. rIICDS = data;
64. rIICCON &= ~0x10; //清中断标志 65. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 66. Delay(1);
67.
68. rIICSTAT = 0xd0; //Stop MasTx condition 69. rIICCON = 0xaf; //Resumes IIC operation. 70. Delay(1);
71.
72.}
73.
74.void Rd24C080(U32 slvAddr, U32 addr, U8 *data) 75.{
76. unsigned char temp; 77. flag=1; //应答标志 78. rIICDS = slvAddr;
79. rIICSTAT = 0xf0; // 主设备发送模式用来发送slvAddr和addr,,启动 80. rIICCON &= ~0x10; //清中断标志 81. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 82. Delay(1);
83.
84. flag =1 ; //readly to translate addr 85. rIICDS = addr;
86. rIICCON &= ~0x10; //清中断标志 87. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 88. Delay(1);
89.
90. flag=1;
91. rIICDS = slvAddr;
92. rIICSTAT = 0xb0; // 主设备接收模式用来接收数据,启动 93. rIICCON &= ~0x10; //清中断标志 94. while(flag == 1) //当发送从地址完成之后会收到ACK信号,在中断处理函数中将该标志置为0 95. Delay(1);
96.
97. //注意:读取下面这个字节必须进行,因为在发送带有读命令的从设备地址后, 98. //AT24C02A会再返回一个从设备地址信息或从设备内存地址信息作为应答,所以一定要把该字节读取后抛弃,因为它不是我们所要读取的信息; 99. flag =1 ; //readly to translate addr 100. temp = rIICDS; // 抛弃第一自己 101. rIICCON &= ~0x10; //清中断标志 102. while(flag) 103. Delay(1);
104.
105. rIICCON = 0x2f; //Resumes IIC operation with NOACK. 106. *data = rIICDS;
107. Delay(1);
108. rIICSTAT = 0x90; //Stop MasTx condition 109. rIICCON = 0xaf; //Resumes IIC operation. 110.
111. Delay(1);
112.
113.}
114.
115.//------------------------------------------------------------------------- 116.void __irq IicInt(void) 117.{
118.
119. rSRCPND = BIT_IIC; //Clear pending bit 120. rINTPND = BIT_IIC;
121. flag = 0;
122.
123.}
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583p4.htm
1.IIS:Inter-IC Sound,是由飞利浦公司开发,该接口支持IIS总线数据格式和MSB-justified数据格式,该接口对FIFO的数据访问采用了DMA模式取代了中断,并且可以在同一时间接收和发送数据
2.IIS:有四根数据线,IISDO,IISDI,IISSCLK,IISLRCK;注意IIS只负责音频信号的传输,如果需要实现录音的话,还需要其他的处理芯片。
IISDO,IISDI用于数据的输入和输出;
IISSCLK:为串行时钟 其频率= 声道数×采用频率×采用位数,假如采用双声道(左右声道),每次采用16位,采样频率40kHz,则IISSCLK =2×16×40 ;
IISLRCK:用于指示左声道或右声道,比如高电平表示正在传输的是左声道数据,实际上IISLRCK频率就等于采用频率;
另外如果是采用了录音芯片的话,比如本例中的UDA1341的话还需要CDCLK为该芯片提供系统同步时钟(编解码时钟)主要用于音频A/D,D/A采用时钟。一般为256fs、384fs或512fs
3.PCLK经过两个预分频器处理后分别得到IISSCLK、IISLRCK和CDCLK(预分频器A得到IISSCLK、IISLRCK,预分频器B得到CDCLK)。如下图:
A和B的分频值是相等的,CDCLK=PCLK/(N+1); 比如:采样fs=44.1kHZ ,CDCLK=384fs,PCLK =50KHZ,从而可以得到N的值
3.UDA134还需要L3(line 3 三根线)总线的支持,由于2440中没有这个总线,我们可以使用IO的方式进行模拟,L3总线包括:L3DATA,L3MODE,L3CLOCK,意思都可以顾名思义了,就不多解释。具体的都可以参考UDA134的数据手册。
UDA134有两种传输模式,地址模式和数据模式,地址模式当然就是地址数据,但是只有高6位表示地址,低2位表示的是传输的模式,是状态模式、数据0模式还是数据1模式,其中状态模式主要用于配置UDA1341的各类初始状态,数据模式主要用于改善音频输入、输出的效果。
L3 MODE为低时是地址模式,L3 MODE为高时是数据传输模式地址模式是用于选择设备和定义目标寄存器,在这种模式下,8位数据的含义是:高6位是设备地址(UDA1341的地址为000101),低两位是后面数据模式下寄存器的类型(00:DATA0,01:DATA1,10:STATUS)。只要没有再改变地址模式下的数据,则数据模式下的数据始终是传输到上一个地址模式所定义的寄存器内。
在传输数据模式下,STATUS是用于设置复位,系统时钟频率、数据输入模式、DC滤波等内容。DATA0分为直接寻址模式和扩展寻址模式,直接寻址模式是直接进行模式的控制,包括音量、静音等等,而扩展寻址模式是在直接寻址模式下先设置3位扩展地址,再在直接寻址模式下设置5位扩展数据。在DATA1下,可以读取到被检测峰值
4.部分代码:
1.#define L3C (1<<4) //gpb4:L3CLOCK
2.#define L3D (1<<3) //gpb3:L3DATA
3.#define L3M (1<<2) //gpb2:L3MODE
4.
5.//通过io口模拟L3总线写数据
2.//mode:1为地址模式,0为数据模式 3.//关于地址模式和数据模式以及传输时序注意参考数据手册 4.static void wrtieL3(U8 data, U8 mode) 5.{
6. int i; 7. if(mode == 1) 8. {
9. rGPBDAT = rGPBDAT&(~(L3D | L3M |L3C)) |L3C; //地址模式,根据手册L3M为LOW,L3C为high 10.
11. } else { 12.
13. rGPBDAT = rGPBDAT & (~(L3D |L3M |L3C)) |(L3M|L3C); //数据模式 L3M为高 14.
15. }
16. Delay(1);
17. //传输数据 18. for(i = 0; i<8; i++) 19. {
20. rGPBDAT &=~L3C;
21. Delay(1);
22. rGPBDAT |=L3D;
23. data>>1;
24.
25. }
26. rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | (L3C | L3M); //L3M=H,L3C=H 27.
28.}
29.
30.
31.//播放音乐,music为存放的数组,length 为音乐的长度 32.void play_music(char *music,int length) 33.{
34. rGPBDAT = rGPBDAT & (~(L3M |L3C |L3D)) |(L3M|L3C); //将L3CLOCK和L3MODE置高,准备开始传输 35. ////根据UDA1341TS数据手册14页中的操作顺序,首先在地址模式下, 36. //选择操作地址000101xx +10(STATUS)=0X16 37. wrtieL3(0x16,1) ;
38. wrtieL3(0x60,0); // 0,1 ,10,000,0 复位 39.
40. wrtieL3(0x16,1) ;
41. WriteL3(0x10,0); //0,0,01, 000,0 : 状态0, 384fs,IIS,no DC-filtering 42.
43. wrtieL3(0x16,1) ;
44. WriteL3(0xc1,0); //1,0,0,0, 0,0,01:状态1, 45. //Gain of DAC 6 dB,Gain of ADC 0dB,ADC non-inverting, 46. //DAC non-inverting,Single speed playback,ADC-Off DAC-On 47. //配置s3c2440的IIS寄存器 48.
49. //预分频器为3,所以CDCLK=PCLK/(3+1)=16.928kHz 50.
51. rIISPSR = 3<<5|3;
52. //无效DMA,输入空闲,预分频器有效 53. rIISCON = (0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1);
54. //PCLK为时钟源,输出模式,IIS模式,每个声道16位,CODECLK=384fs,SCLK=32fs 55. rIISMOD = (0<<9)|(0<<8)|(2<<6)|(0<<5)|(0<<4)|(1<<3)|(1<<2)|(1<<0);
56. rIISFCON = (0<<15)|(1<<13); //输出FIFO正常模式,输出FIFO使能 57. flag=1;
58. count=0;
59. //开启IIS 60. rIISCON |= 0x1;
61. while(flag) 62. {
63. if((rIISCON & (1<<7))==0) //检查输出FIFO是否为空 64. {
65. //FIFO中的数据为16位,深度为32 66. //当输出FIFO为空时,一次性向FIFO写入32个16位数据 67.
68. for(i=0;i<32;i++) 69. {
70. rIISFIFO=(buffer[2*i+count])+(buffer[2*i+1+count]<<8);
71. }
72. count+=64;
73. if(count>length) 74. flag=0; //音频数据传输完,则退出 75. }
76. }
77.
78. rIISCON = 0x0; //关闭IIS 79.
80.}
81.// init the conrresponding pins 82.void IIS_test() 83.{
84. //fs=44.1kHZ,CODECLK =384fs=16.9344MHZ 85.
86. //配置L3接口总线,GPB2:L3MODE, GPB3:L3DATA, GPB4:L3CLOCK 87. rGPBCON = rGPBCON & ((0<<4) | (0<<6) |(0<<8));
88. rGPBCON = rGPBCON |((1<<4) |(1<<6) |(1<<8));
89.
90. rGPBUP = 0x7ff;
91. rGPBDAT = 0x1e4;
92.
93. rGPEUP |=0x1f; //disable pull up 94. rGPECON = rGPECON & ~(0x3ff) | 0x2aa;
95.
96. play_music();
97.
98.}
参考 http://www.linuxidc.com/Linux/2011-06/37584.htm
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583p5.htm
1.Nand flash以page为单位进行读写,以block为单位进行擦除,没页分为main区和spare区,main区用于存放正常的数据,spare区用于存放一些附加信息
2.S3c2440 支持从Nand 启动是因为内部有一个叫做Steppingstone的SRAM buffer,当启动的时候,nand 的前4k的将会代码将被拷贝到steppingstone中执行,注意前4k代码是不会经过ECC校验的,所以必须确保这些代码的准确
3.对nand的操作都是通过使用命令来实现,有的操作只要一个命令就可以完成,而有的需要两个命令才能完成,下面是K9F1G08U0B的命令表:
4.关于TACLS,TWRPH0,TWRH1几个参数的数值问题:
从下面的时序图不难看出这几个参数的意思,在此就不赘述。
图1 (s3c2440)
图2(K9F1G08U0B)
比较上面两个时序图,我们发现,TWRPH0 即为K9F1G08U0B 中的twp, TWRH1 为tCLH
TACLS为tcls – twp,K9F1G08U0B的手册给出了这些参数的最小时间,如图
1.#include "def.h"
2.#include "2440addr.h"
3.#include "mynand.h"
4.
5.//define command 6.#define CMD_READ1 0x00 //页读命令1
7.#define CMD_READ2 0x30 //页读命令2
8.#define CMD_READID 0x90 //读取ID
9.#define CMD_RESET 0xff //复位
10.#define CMD_WRITE1 0x80 //页写命令1
11.#define CMD_WRITE2 0x10 //页写命令2
12.#define CMD_ERASE1 0x60 //块擦除命令1
13.#define CMD_ERASE2 0xd0 //块擦除命令2
14.#define CMD_STATUS 0x70 //读取状态命令
15.#define CMD_RANDOMREAD1 0X05 //随机读取命令1
16.#define CMD_RANDOMREAD2 0xe0 //随机读取命令2
17.#define CMD_RANDOMWRITE 0x85 //随机写命令
18.
19.#define NF_CE_L() {rNFCONT &=~(1<<1);} //使能片选
20.#define NF_CE_H() {rNFCONT |=(1<<1);} //关闭片选
21.#define NF_MECC_UnLock() {rNFCONT&=~(1<<5);} //解锁main去ECC
22.#define NF_MECC_Lock() {rNFCONT|=(1<<5);} //锁定main去ECC
23.#define NF_SECC_UnLock() {rNFCONT &= ~(1<<6); } //解锁spare区ECC
24.#define NF_SECC_Lock() {rNFCONT |= (1<<6); } //锁定spare区ECC
25.#define NF_RSTECC() {rNFCONT |= (1<<4); } //复位ECC
26.
27.
28.
29.#define NF_WAITRB() {while(!(rNFSTAT &(1<<0))) ;} //等待nand flash 空闲
30.#define NF_CLEAR_RB() {rNFSTAT |= (1<<2); } //清除RnB信号
31.#define NF_DETECT_RB() {while(!(rNFSTAT&(1<<2)));}
32.
33.#define NF_RDDATA8() ((*(volatile unsigned char*)0x4E000010) )
34.#define NF_CMD(cmd) {rNFCMD = (cmd);} //命令
35.#define NF_ADDR( addr) {rNFADDR = (addr);} //地址
36.#define NF_RDDATA() (rNFDATA) //读取32位数据
37.//#define NF_RDDATA8() (rNFDATA) //读取8位数据 38.#define NF_WRDATA(data) { rNFDATA = (data);} //写32位数据
39.#define NF_WRDATA8(data) { rNFDATA8 = (data);} //写8位数据
40.
41.#define TACLS 1
42.#define TWRPH0 2
43.#define TWRPH1 0
44.
45.extern void Delay(int time); 46.void nand_init(void) 47.{
48. rGPACON = rGPACON & (~(0x3f<<17)) |(0x3f<<17) ;
49. rNFCONF = (TACLS<<12) |(TWRPH0<<8) |(TWRPH1<<4) |(0<<0) ;
50. //非锁定,屏蔽nandflash中断,初始化ECC及锁定main区和spare区ECC,使能nandflash片选及控制器 51. rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);
52.
53.
54.}
55.
56.//复位nand 57. void nand_reset() 58.{
59. int i; 60. NF_CE_L(); //cs 61. NF_CLEAR_RB(); //清除RnB信号 62. for(i=0;i<10;i++) ; 63. NF_CMD(CMD_RESET); //写入复位命令 64. NF_DETECT_RB(); //等待RnB信号变高,即不忙 65. NF_CE_H(); //关闭nandflash片选 66.
67.}
68.
69.
70.//读取nand的id号,首先需要写入读ID命令,然后再写入0x00地址,就可以读取到一共五个周期的芯片ID,第一个周期为厂商ID,第二个周期为设备ID, 71.//第三个周期至第五个周期包括了一些具体的该芯片信息 72. U8 read_id(void) 73.{
74. int i; 75. U8 first, second, third, forth, fifth; //分别读取1---5个周期的数据 76. NF_CE_L(); //cs 77. //NF_CLEAR_RB(); //清除RnB信号 78. // for(i=0;i<10;i++) ; 79. NF_CMD(CMD_READID); //读ID命令 80. NF_ADDR(0x0); //写0x00地址 81.
82. first = NF_RDDATA8() ; //厂商ID: 0Xec 83. second = NF_RDDATA8() ; //设备ID,一般从这个参数可以判断出nand的一些参数 84. third = NF_RDDATA8() ; //0x00 85. forth = NF_RDDATA8() ; //0x95 86. fifth = NF_RDDATA8() ; // 0x40 87.
88. NF_CE_H(); //关闭nandflash片选 89. return second; 90.}
91.
92.//带硬件ECC校验的读,page_numer为页号,每页2K 93.U8 nand_readpage_ecc(U32 page_number, U8 *buffer)
94.{
95. int i; 96. U32 mainecc0, spareecc; //用于存放ecc的临时值 97. NF_RSTECC(); //复位ECC 98. NF_MECC_UnLock() //解锁主区ECC 99. NF_CE_L(); //cs 100. NF_CLEAR_RB(); //清除RnB信号 101. NF_CMD(CMD_READ1); //页读命令周期1 102. //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号) 103. NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化 104. NF_ADDR(0x00); //列地址A8~A11 105.
106. // page = page_number/4; 107. // data_addr = 512 *(page_number%4); 108. // NF_ADDR(data_addr&0xff); 109. // NF_ADDR((data_addr>>8)&0xff); 110.
111. NF_ADDR((page_number) & 0xff); //行地址A12~A19 112. NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27 113.
114. NF_CMD(CMD_READ2); //页读命令周期2 115. NF_DETECT_RB(); //等待RnB信号变高,即不忙 116.
117. for(i=0;i<2048;i++) 118. buffer[i] = NF_RDDATA8() ;
119. NF_MECC_Lock(); //锁定main区ECC值 120. NF_SECC_UnLock(); //解锁spare区ECC 121.
122. mainecc0=NF_RDDATA(); //读spare区的前4个地址内容,即第2048~2051地址,这4个字节为main区的ECC 123. //把读取到的main区的ECC校验码放入NFMECCD0/1的相应位置内 124. rNFMECCD0=((mainecc0&0xff00)<<8)|(mainecc0&0xff);
125. rNFMECCD1=((mainecc0&0xff000000)>>8)|((mainecc0&0xff0000)>>16);
126.
127. NF_SECC_Lock(); //锁定spare区的ECC值 128. spareecc=NF_RDDATA(); //继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值 129. //把读取到的spare区的ECC校验码放入NFSECCD的相应位置内 130. rNFSECCD=((spareecc&0xff00)<<8)|(spareecc&0xff);
131. NF_CE_H(); //关闭nandflash片选 132.
133. //判断所读取到的数据是否正确 134. if ((rNFESTAT0&0xf) == 0x0) 135. return 0x66; //正确 136. else 137. return 0x44; 138.}
139.
140.U8 nand_writepage_ecc(U32 page_number, U8 *buffer)
141.{
142. int i,stat; 143. U32 mecc0, secc; //用于存放ecc的临时值 144. char ECCBuf[10]; 145. i = nand_is_badblock(page_number>>6) ;
146. if( i ==0x33) 147. return 0x42 ; //坏块 148.
149. NF_RSTECC(); //复位ECC 150. NF_MECC_UnLock() //解锁主区ECC 151. NF_CE_L(); //cs 152. NF_CLEAR_RB(); //清除RnB信号 153. NF_CMD(CMD_WRITE1); //页读命令周期1 154. //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号) 155. NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化 156. NF_ADDR(0x00); //列地址A8~A11 157.
158. // page = page_number/4; 159. // data_addr = 512 *(page_number%4); 160. // NF_ADDR(data_addr&0xff); 161. // NF_ADDR((data_addr>>8)&0xff); 162.
163. NF_ADDR((page_number) & 0xff); //行地址A12~A19 164. NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27 165.
166. for(i=0;i<2048;i++) 167. NF_WRDATA8(buffer[i]);
168.
169. NF_MECC_Lock(); //锁定main区ECC值 170.
171. mecc0=rNFMECC0; //读取main区的ECC校验码 172. //把ECC校验码由字型转换为字节型,并保存到全局变量数组ECCBuf中 173. ECCBuf[0]=(U8)(mecc0&0xff);
174. ECCBuf[1]=(U8)((mecc0>>8) & 0xff);
175. ECCBuf[2]=(U8)((mecc0>>16) & 0xff);
176. ECCBuf[3]=(U8)((mecc0>>24) & 0xff);
177.
178. NF_SECC_UnLock(); //解锁spare区ECC 179. for(i=0;i<4;i++) 180. {
181. NF_WRDATA8(ECCBuf[i]);
182. }
183.
184. NF_SECC_Lock(); //锁定spare区的ECC值 185. secc=rNFSECC; //读取spare区的ECC校验码 186. //把ECC校验码保存到全局变量数组ECCBuf中 187. ECCBuf[4]=(U8)(secc&0xff);
188. ECCBuf[5]=(U8)((secc>>8) & 0xff);
189. //把spare区的ECC值继续写入到spare区的第2052~2053地址内 190.
191. for(i=4;i<6;i++) 192. {
193. NF_WRDATA8(ECCBuf[i]);
194. }
195.
196. NF_CMD(CMD_WRITE2); //页读命令周期2 197. Delay(100);
198.
199. NF_CMD(CMD_STATUS); //读状态命令 200. //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同 201. do { 202. stat = NF_RDDATA8();
203.
204. }while(!(stat&0x40)); 205.
206. NF_CE_H(); //关闭nandflash片选 207.
208. //判断所读取到的数据是否正确 209. if (stat & 0x1) 210. {
211. i = rNF_MarkBadBlock(page_number>>6); //标注该页所在的块为坏块 212. if (i == 0x21) 213. return 0x43 ; //标注坏块失败 214. else 215. return 0x44; //写操作失败 216. }
217. else 218. return 0x66; 219.
220.
221.}
222.
223.
224.U8 nand_random_readpage(U32 page_number, U32 add)
225.{
226.
227. NF_CE_L(); //cs 228. NF_CLEAR_RB(); //清除RnB信号 229. NF_CMD(CMD_READ1); //页读命令周期1 230. //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号) 231. NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化 232. NF_ADDR(0x00); //列地址A8~A11 233.
234. // page = page_number/4; 235. // data_addr = 512 *(page_number%4); 236. // NF_ADDR(data_addr&0xff); 237. // NF_ADDR((data_addr>>8)&0xff); 238.
239. NF_ADDR((page_number) & 0xff); //行地址A12~A19 240. NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27 241.
242. NF_CMD(CMD_READ2); //页读命令周期2 243. NF_DETECT_RB(); //等待RnB信号变高,即不忙 244.
245. NF_CMD(CMD_RANDOMREAD1); //随意读命令周期1 246. //页内地址 247. NF_ADDR((char)(add&0xff)); //列地址A0~A7 248. NF_ADDR((char)((add>>8)&0x0f)); //列地址A8~A11 249. NF_CMD(CMD_RANDOMREAD2); //随意读命令周期2 250. return NF_RDDATA8(); //读取数据 251.
252.
253.}
254.
255.
256.
257.
258.U8 nand_random_writepage(U32 page_number, U32 add, U8 data)
259.{
260. U8 stat;
261. NF_CE_L(); //cs 262. NF_CLEAR_RB(); //清除RnB信号 263. NF_CMD(CMD_WRITE1); //页读命令周期1 264. //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号) 265. NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化 266. NF_ADDR(0x00); //列地址A8~A11 267.
268. // page = page_number/4; 269. // data_addr = 512 *(page_number%4); 270. // NF_ADDR(data_addr&0xff); 271. // NF_ADDR((data_addr>>8)&0xff); 272.
273. NF_ADDR((page_number) & 0xff); //行地址A12~A19 274. NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27 275.
276. NF_CMD(CMD_RANDOMWRITE); //页读命令周期2 277.
278. //页内地址 279. NF_ADDR((char)(add&0xff)); //列地址A0~A7 280. NF_ADDR((char)((add>>8)&0x0f)); //列地址A8~A11 281. NF_WRDATA8(data); //写入数据 282. NF_CMD(CMD_WRITE2); //页写命令周期2 283.
284. //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同 285. do { 286. stat = NF_RDDATA8();
287.
288. }while(!(stat&0x40)); 289.
290. NF_CE_H(); //关闭nandflash片选 291.
292. //判断所读取到的数据是否正确 293. if (stat & 0x1) 294. return 0x44; 295. else 296. return 0x66; 297.
298.}
299.
300.U8 nand_is_badblock(U32 block)
301.{
302. return nand_random_readpage(block*64, 2054); 303.
304.}
305.
306.U8 rNF_MarkBadBlock(U32 block)
307.{
308. U8 result;
309. result = nand_random_writepage(block*64, 2054, 0x33);
310. if(result == 0x44) 311. return 0x21; //写坏块标注失败 312. else 313. return 0x60 ; 314.
315.}
都是以ns为单位的。Nand flash 控制器使用的是HCLK ,此时为100MHZ。一个周期为10ns。给本例中我们设置:TACLS为1,TWRPH0为2,TWRH1为0;只要注意设置大于上面的最小值就ok了。
6.ECC校验编程:由于使用软件的方法进行ECC校验比较复杂,S3C2440中自带了硬件产生ECC校验,可以通过NFCONT的[5]和[6]位来分别开启硬件ECC产生器,如果是8bit的nand flash接口将产生4个字节的main 区 ECC 校验码和2个字节的spare区 校验码,分别存在NFMECCD和NFSECCD中,当然前提是开启了对应区的ECC发生器。具体可以参考2440的手册P220.下面是编程ECC的过程:
(1)写NFCONT[4]为1初始化ECC 编解码器,写NFCONT[5]为0解锁main区ECC发生器
(2)当读写玩数据时写NFCONT[5]为1来锁定ECC防止被改变
(3)通常用户会将main区产生的ECC校验码写到spare区,这些数据和NFMECC0/1中的数据是相同的。本例中我们约定每一页的spare区的第0个地址到第3个地址存储main区ECC,第4个地址和第5个地址存储spare区ECC
(4)硬件自动产生的ECC会自动保存在NFMECC中,而NFMECCD0/1中的数据需要用户自己写入,当放入数据后,系统会自动比较NFMECC0/1和NFMECCD0/1的内容,这样就实现了ECC码的校验
(5) 最后我们就可以通过读取NFESTAT0/1(因为K9F1G08U0B是8位IO口,因此这里只用到了NFESTAT0)中的低4位来判断读取的数据是否正确,其中第0位和第1位为main区指示错误,第2位和第3位为spare区指示错误
下面是核心代码:
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-06/37583p6.htm
lcd只要搞清楚了时序其实还是比较简单的,现在LINUX下的LCD驱动如果是像三星的处理器都有很好的支持,只需要修改一些参数就OK了,如果是芯片级的驱动那是比较复杂的,主要是涉及到Framebuffer会让人很抓狂,等完成了裸机的驱动我将会开始写linux的驱动程序编写的文章,请各位网友关注并提出宝贵意见!!!
先来看一下s3c2440中LCD控制器的结构:
从上面结构图可以看出:整个lcd控制器大致可以由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS寄存器几个部分组成。
1。REGBANK由17个可编程的寄存器组和一块256*16的调色板内存组成,它们用来配置LCD控制器的
2。LCDCDMA是一个专用的DMA,它能自动地把在侦内存中的视频数据传送到LCD驱动器,通过使用这个DMA通道,视频数据在不需要 CPU的干预的情况下显示在LCD屏上
3.VIDPRCS接收来自LCDCDMA的数据,将数据转换为合适的数据格式,比如说4/8位单扫,4位双扫显示模式,然后通过数据端口VD[23:0]传送视频数据到LCD驱动器
4.TIMEGEN由可编程的逻辑组成,他生成LCD驱动器需要的控制信号,比如VSYNC、HSYNC、VCLK和LEND等等,而这些控制信号又与REGBANK寄存器组中的LCDCON1/2/3/4/5的配置密切相关,通过不同的配置,TIMEGEN就能产生这些信号的不同形态,从而支持不同的LCD驱动器(即不同的STN/TFT屏)。
LCD提供的外部接口信号:
1.VSYNC/VFRAME/STV:垂直同步信号(TFT)/帧同步信号(STN)/SEC TFT信号
2.HSYNC/VLINE/CPV:水平同步信号(TFT)/行同步脉冲信号(STN)/SEC TFT信号
3.VD[23:0]:LCD像素数据输出端口(TFT/STN/SEC TFT);
4。VDEN/VM/TP:数据使能信号(TFT)/LCD驱动交流偏置信号(STN)/SEC TFT 信号
说明:
A:显示指针从矩形左上角的第一行第一个点开始,一个点一个点的在LCD上显示,在上面的时序图上用时间线表示就为VCLK,我们称之为像素时钟信号;
B:当显示指针一直显示到矩形的右边就结束这一行,那么这一行的动作在上面的时序图中就称之为1 Line;
C:接下来显示指针又回到矩形的左边从第二行开始显示,注意,显示指针在从第一行的右边回到第二行的左边是需要一定的时间的,我们称之为行切换;
D:如此类推,显示指针就这样一行一行的显示至矩形的右下角才把一副图显示完成。因此,这一行一行的显示在时间线上看,就是时序图上的HSYNC
E:然而,LCD的显示并不是对一副图像快速的显示一下,为了持续和稳定的在LCD上显示,就需要切换到另一幅图上(另一幅图可以和上一副图一样或者不一样,目的只是为了将图像持续的显示在LCD上)。那么这一副一副的图像就称之为帧,在时序图上就表示为1 Frame,因此从时序图上可以看出1 Line只是1 Frame中的一行;
F:同样的,在帧与帧切换之间也是需要一定的时间的,我们称之为帧切换,那么LCD整个显示的过程在时间线上看,就可表示为时序图上的VSYNC
下面是时序图:
VBPD(vertical back porch):表示在一帧图像开始时,垂直同步信号以后的无效的行数,对应驱动中的upper_margin;
VFBD(vertical front porch):表示在一帧图像结束后,垂直同步信号以前的无效的行数,对应驱动中的lower_margin;
VSPW(vertical sync pulse width):表示垂直同步脉冲的宽度,用行数计算,对应驱动中的vsync_len;
HBPD(horizontal back porch):表示从水平同步信号开始到一行的有效数据开始之间的VCLK的个数,对应驱动中的left_margin;
HFPD(horizontal front porth):表示一行的有效数据结束到下一个水平同步信号开始之间的VCLK的个数,对应驱动中right_margin;
HSPW(horizontal sync pulse width):表示水平同步信号的宽度,用VCLK计算,对应驱动中的hsync_len;
行频和场频的计算:
行频 = VCLK /[(HSPW+1)+(HSPD+1)+(HFPD+1)+(HOZVAL+1)]
场频 =行频 /[(VSPW+1)+(VBPD+1)+(VFPD+1)+(LINEVAL+1)]
接下来了解下相关寄存器的字段结构:
对于以上这些参数的值将分别保存到REGBANK寄存器组中的LCDCON1/2/3/4/5寄存器中
LCDCON1:LINECNT18--27设置屏的行数,最大1024行,从LINECNT计数到0,真正要显示区域的大小是在LCDCON2中的LINEVAL设置;CLKVAL 8--17用于设置分频因子,STN: VCLK = HCLK / (CLKVAL x 2) ( CLKVAL ≥2 ) TFT: VCLK = HCLK / [(CLKVAL+1) x 2] ( CLKVAL ≥ 0 ); 6 - 5位扫描模式(对于STN屏:4位单/双扫、8位单扫); 4 - 1位色位模式(1BPP、8BPP、16BPP等);
LCDCON2:用于设置VBPD,LINEVAL,VFBD,VSPW,对于STN屏VBPD,VFBD,VSPW值都为0。
LCDCON3:功能类似LCDCON2,只是用于垂直方向的一些参数。
LCDCON5:用于查询和设置一些状态信息。
编程步骤:
1、打开LCD背光
将LCD背光对应的GPIO设置为禁止上拉(GPxUP相应位写入1),选择output类型(GPxCON相应位写入01),输出为高电平(GPxDAT相应位写入1)。
2、打开LCD电源
可以将GPG4选择为LCD_PWREN(GPGCON:9-8写入11),这时候LCD电源的打开/关闭可以通过LCDCON5:3来控制。
也可以自定义其他GPIO用作LCD电源开关,只需将此GPIO设置为禁止上拉(GPxUP相应位写入1),选择output类型(GPxCON相应位写入01),输出为高电平(GPxDAT相应位写入1)打开LCD电源。
3、设置其他信号线
其他信号线包括VD0-VD23和VFRAME、VLINE、VCLK等,分别在GPCCON,GPDCON中选择相应功能。
4、设置LCD的频率(VCLK)
LCD的Datasheet上一般会写有一个推荐的频率,比如我使用的屏幕推荐频率为6.4M,我需要通过一些计算选择一个合适的CLKVAL以产生这个频率:
对于TFT LCD,S3C2440提供的VCLK的计算公式为:
VCLK = HCLK / ((CLKVAL+1)*2)
可以得出:
CLKVAL = HCLK / (VCLK * 2) - 1
我的HCLK是100Mhz(CPU运行在400Mhz, CLKDIV_VAL设置为5,Fclk:Hclk:Pclk = 1:4:8),VCLK使用屏幕推荐的6.4M,得到:
CLKVAL = 100000000 / (6400000 * 2) - 1 = 6.8
选择最接近的整数值7,写入LCDCON1:17-8。
(VCLK其实就是根据 每秒帧数*帧行数*行像素 计算出来的,帧行数和行像素需要包含空白数和同步数)
5、设置其他相关参数
LCD相关的参数主要还有这几个:
LINEVAL: LCD水平像素-1,如320-1 = 319
HOZVAL: LCD垂直像素-1,如240-1 = 239
HFPD: 行开始前的VCLK时钟数(LCD屏幕的Datasheet一般有推荐值)
HBPD: 行结束后的VCLK时钟数(LCD屏幕的Datasheet一般有推荐值)
HSPW: 行之间水平同步的无效VCLK时钟数(LCD屏幕的Datasheet一般有推荐值)
VFPD: 帧数据开始前的空白行数(LCD屏幕的Datasheet一般有推荐值)
VBPD: 帧数据结束后的空白行数(LCD屏幕的Datasheet一般有推荐值)
VSPW: 帧之间垂直同步的无效行数(LCD屏幕的Datasheet一般有推荐值)
(相关寄存器LCDCON2, LCDCON3, LCDCON4)
6、设置视频缓冲区的地址
2440支持虚拟屏幕,可以通过改变LCD寄存器实现屏幕快速移动
PAGEWIDTH:虚拟屏幕一行的字节数,如果不使用虚拟屏幕,设置为实际屏幕的行字节数,如16位宽320像素,设为320 * 2
OFFSIZE:虚拟屏幕左侧偏移的字节数,如果不使用虚拟屏幕,设置为0
LCDBANK: 视频帧缓冲区内存地址30-22位
LCDBASEU: 视频帧缓冲区的开始地址21-1位
LCDBASEL: 视频帧缓冲区的结束地址21-1位
(相关寄存器LCDSADDR1,LCDSADDR2,LCDSADDR3)
7、确定信号的极性
2440的LCD控制器允许设置VCLK、VLINE、VFRAME等信号的极性(上升沿有效还是下降沿有效),需要对照LCD的Datasheet一一确认。
(相关寄存器LCDCON5)
8、禁止LPC3600/LCC3600模式!
如果不是使用三星LPC3600/LCC3600 LCD,必须禁止LPC3600/LCC3600模式(写入0到TCONSEL)!
9、打开视频输出
ENVID设为1 (LCDCON1:0写入1)
核心代码:(特别注意LCD_BUFFER[LCD_HEIGHT][LCD_WIDTH]中LCD_HEIGHT对应下面rLCDCON2中的行数LINEVAL)
- #include "def.h"
- #include "lcd.h"
- #include "2440addr.h"
-
- #define LCD_WIDTH 240
- #define LCD_HEIGHT 320
- #define LINEVAL (LCD_HEIGHT - 1)
- #define HOZVAL (LCD_WIDTH - 1)
-
-
- //水平同步信号
- #define LCD_HSYNC 5
- #define LCD_LEFT_MARGIN 19
- #define LCD_RIGHT_MARGIN 36
-
- //垂直同步信号
- #define LCD_VSYNC 1
- #define LCD_UPPER_MARGIN 1
- #define LCD_LOWER_MARGIN 5
-
- #define LCD_CLKVAL 4 //时钟分频因子,VCLK = HCLK/((LCD_CLKVAL+1)*2)
- #define BPPMODE_TFT 12 //16位TFT
- //for LCDCON5
- #define PNRMODE 3 //显示模式:TFT LCD Panel
- #define BPP24BL 1 //24位输出格式MSB Valid
- #define FRM565 1 //16位输出格式:5:6:5
- #define INVVCLK 0 //下降沿有效
- #define INVVFRAME 1 //翻转VSYNC信号
- #define INVVLINE 1 //翻转HSYNC信号
- #define PWREN 1 //使能PWREN信号
- #define INVVD 0 //正常VD信号极性
- #define INVVDEN 0 //正常VDEN信号极性
- #define INVVDEN 0 //正常VDEN信号极性
- #define BSWP 0 //颜色数据字节不交换
- #define HWSWP 0 //颜色数据半字不交换
-
- //注意这里的LCD_HEIGHT对应下面rLCDCON2中的行数LINEVAL,否则会出错
- volatile static unsigned short LCD_BUFFER[LCD_HEIGHT][LCD_WIDTH];
- extern const unsigned char sunflower_240x320[];
-
- #define M5D(n) ((n)&0x1fffff) //得到n的低21位
- #define LCD_ADDR ((U32)LCD_BUFFER)
-
- static void LCD_Port_Init()
- {
- //*** PORT C GROUP
- //Ports : GPC15 GPC14 GPC13 GPC12 GPC11 GPC10 GPC9 GPC8 GPC7 GPC6 GPC5 GPC4 GPC3 GPC2 GPC1 GPC0
- //Signal : VD7 VD6 VD5 VD4 VD3 VD2 VD1 VD0 LCDVF2 LCDVF ** VM VFRAME VLINE VCLK ***
- //Binary : 10 10 , 10 10 , 10 10 , 10 01 , 00 00 , ** 10 , 10 10 , 10 **
- // rGPCCON = 0xaaa956aa;
- rGPCCON = 0xaaaa02a8;
- rGPCUP = 0xffff; // The pull up function is disabled GPC[15:0]
-
- //*** PORT D GROUP
- //Ports : GPD15 GPD14 GPD13 GPD12 GPD11 GPD10 GPD9 GPD8 GPD7 GPD6 GPD5 GPD4 GPD3 GPD2 GPD1 GPD0
- //Signal : VD23 VD22 VD21 VD20 VD19 VD18 VD17 VD16 VD15 VD14 VD13 VD12 VD11 VD10 VD9 VD8
- //Binary : 10 10 , 10 10 , 10 10 , 10 10 , 10 10 , 10 10 , 10 10 ,10 10
- rGPDCON = 0xaaaaaaaa;
- rGPDUP = 0xffff; // The pull up function is disabled GPD[15:0]
-
- }
- void LCD_Init(void)
- {
- LCD_Port_Init();
- rLCDCON1 = (LCD_CLKVAL << 8) | (PNRMODE<<5) | (BPPMODE_TFT <<1);
- rLCDCON2 = (LCD_UPPER_MARGIN << 24) |(LINEVAL << 14) |(LCD_LOWER_MARGIN << 6) | (LCD_VSYNC<<0);
- rLCDCON3 = (LCD_LEFT_MARGIN <<19) | (HOZVAL<<8) |(LCD_RIGHT_MARGIN<<0);
- rLCDCON4 = (LCD_HSYNC<<0);
- rLCDCON5 = (FRM565 << 11)|(INVVCLK<<10) | (INVVLINE<<9) | (INVVFRAME<<8) | (0<<7) | (INVVDEN<<6) | (PWREN<<3) |(BSWP<<1) | (HWSWP);
- //将缓存首地址的A[30:22]存在寄存器[29:21]中,数据高地址A[21:1]存在寄存器[20:0]中
- rLCDSADDR1 = ((LCD_ADDR>>22)<<21) | ((M5D(LCD_ADDR >> 1)) << 0);
- //将缓存的低地址A[21:1]写入寄存器,
- rLCDSADDR2 = M5D((LCD_ADDR + LCD_WIDTH * LCD_HEIGHT * 2) >> 1);
- //LCD_WIDTH×16/16;由于是选择的16位模式,
- //如果是24位模式,每个像素4字节则为LCD_WIDTH×32/16
- rLCDSADDR3 = LCD_WIDTH;
- rLCDINTMSK|=(3); // 屏蔽LCD中断
- rTCONSEL = 0; //无效LPC3480
-
- rLCDCON1|=1; // ENVID=ON
-
- }
-
- //背光开启
- static void LCD_Backlight(void)
- {
- //GPG4 configure as LCD_PWREN
- rGPGCON =rGPGCON &(~(3<<8)) | (3<<8) ;
- rGPGUP =rGPGUP &(~(1<<4)) | (1<<4);
- rGPGDAT = rGPGDAT | (1<<4) ;
-
-
- //Enable LCD POWER ENABLE Function
- rLCDCON5 = rLCDCON5&(~(1<<3))|(1<<3); // 有效PWREN信号
- rLCDCON5 = rLCDCON5&(~(1<<5))|(0<<5); // PWREN信号极性不翻转
-
- }
-
-
-
-
- /**************************************************************
- 320×240 TFT LCD单个象素的显示数据输出
- **************************************************************/
- static void PutPixel(U32 x,U32 y,U16 c)
- {
- if(x
- LCD_BUFFER[(y)][(x)] = c;
- }
-
- /**************************************************************
- 320×240 TFT LCD全屏填充特定颜色单元或清屏
- **************************************************************/
- static void Lcd_ClearScr( U16 c)
- {
- unsigned int x,y ;
-
- for( y = 0 ; y < LCD_HEIGHT ; y++ )
- {
- for( x = 0 ; x < LCD_WIDTH ; x++ )
- {
- LCD_BUFFER[y][x] = c ;
- }
- }
- }
-
-
- /**************************************************************
- 在LCD屏幕上用颜色填充一个矩形
- **************************************************************/
- void Glib_FilledRectangle(int x1,int y1,int x2,int y2, U16 color)
- {
- int i;
- int j;
- //for(i=y1;i<=y2;i++)
- //Glib_Line(x1,i,x2,i,color);
- for(i=x1;i
- for(j=y1;j
- PutPixel(i,j,color);
- }
-
-
-
- /**************************************************************
- 在LCD屏幕上指定坐标点画一个指定大小的图片
- **************************************************************/
- static void Paint_Bmp(int x0,int y0,int h,int l,const unsigned char *bmp)
- {
- int x,y;
- U32 c;
- int p = 0;
-
- for( y = 0 ; y < l ; y++ )
- {
- for( x = 0 ; x < h ; x++ )
- {
- c = bmp[p+1] | (bmp[p]<<8) ;
-
- if ( ( (x0+x) < LCD_WIDTH) && ( (y0+y) < LCD_HEIGHT) )
- LCD_BUFFER[y0+y][x0+x] = c ;
-
- p = p + 2 ;
- }
- }
- }
-
-
- void LCD_test(void)
- {
- LCD_Init();
- LCD_Backlight(); //开启背光
-
- // Glib_FilledRectangle(10,10,100,100,0xffe0);
- Paint_Bmp(0, 0, 240, 320, sunflower_240x320);
-
-
-
- }