Like Tree5Likes

Interface DS1302(RTC) with 8051 to run time on lcd. Time run but got Problem,help pls

This is a discussion on Interface DS1302(RTC) with 8051 to run time on lcd. Time run but got Problem,help pls within the C Programming forums, part of the General Programming Boards category; ----------------------------------------------------------------------- For all the 1,3,5,7,9mins and so on...or maybe i should say is the odd mins. It will display something ...

  1. #1
    Registered User
    Join Date
    Apr 2011
    Posts
    21

    Interface DS1302(RTC) with 8051 to run time on lcd. Time run but got Problem,help pls

    -----------------------------------------------------------------------

    For all the 1,3,5,7,9mins and so on...or maybe i should say is the odd

    mins.
    It will display something like the patterns shown below for the 1

    minute.

    Here is a example of 85error that it display.

    The first 5 patterns of the "1 hr and 5min
    "01:85:00"
    "01:05:01"
    "01:85:02"
    "01:05:03"
    "01:85:04"

    -----------------------------------------------------------------------

    As for all the 2,4,6,8,10mins and so on...or maybe i should say is the

    even mins.
    It will display something like the patterns shown below for the first

    59 second and the 1 hour.




    For the first 59 second,i show first five patterns of it.
    "00:85:00"
    "85:00:01"
    "00:85:02"
    "85:00:03"
    "00:85:04"
    .
    .
    .
    .

    "85:00:59"


    For the starting of 1 minute,i show first five patterns of it.
    "00:85:00"
    "00:01:01"
    "00:85:02"
    "00:01:03"
    "00:85:04"
    .
    .
    .
    .



    For the starting of 1 hour,i show first five patterns of it.
    "01:85:00"
    "85:00:01"
    "01:85:02"
    "85:00:03"
    "01:85:04"
    .
    .
    .
    .


    -----------------------------------------------------------------------


    On top is the error that i face,im not sure how can i solve it.

    The DS1302 header file,i got on a website and i have modify it to run time on the LCD.

    What can be the problem that causing this??

    Please let me know anything that i could try to solve this problem.
    Please,I need some guide and help with it. I cant figure yet..

    I will paste the header file. Let me know if need to see my c code
    ~~ THx ~~


    [tag]
    Code:
    #ifndef _REAL_TIMER_DS1302_2003_7_21_ 
    #define _REAL_TIMER_DS1302_2003_7_21_ 
     
    #define  DS1302_CLK 	P1_7              //实时时钟时钟线引脚  
    #define  DS1302_IO 		P1_6              //实时时钟数据线引脚  
    #define  DS1302_RST 	P1_5              //实时时钟复位线引脚 
    sbit  ACC0  =			ACC^0 ;
    sbit  ACC7  =			ACC^7  ;
     
    typedef struct __SYSTEMTIME__ 
    { 
    	unsigned char Second; 
    	unsigned char Minute; 
    	unsigned char Hour; 
    	unsigned char Week; 
    	unsigned char Day; 
    	unsigned char Month; 
    	unsigned char  Year; 
    	unsigned char DateString[9]; 
    	unsigned char TimeString[9]; 
    }SYSTEMTIME;	//定义的时间类型 
     
    #define AM(X)	X 
    #define PM(X)	(X+12)            	  // 转成24小时制 
    #define DS1302_SECOND	0x80 		  
    #define DS1302_SECOND_READ	0x81 
    #define DS1302_MINUTE	0x82
    #define DS1302_MINUTE_READ	0x83 
    #define DS1302_HOUR		0x84
    #define DS1302_HOUR_READ	0x85  
    #define DS1302_WEEK		0x8A
    #define DS1302_WEEK_READ	0x8B
    #define DS1302_DAY		0x86 
    #define DS1302_DAY_READ		0x87 
    #define DS1302_MONTH	0x88 
    #define DS1302_MONTH_READ	0x89 
    #define DS1302_YEAR		0x8C 
    #define DS1302_YEAR_READ    0x8D 
    #define DS1302_RAM(X)	(0xC0+(X)*2)   	//用于计算 DS1302_RAM 地址的宏
    
    
    void DS1302InputByte(unsigned char d) 	//实时时钟写入一字节(内部函数) 
    {  
        unsigned char i; 
        ACC = d; 
        for(i=8; i>0; i--) 
        { 
            DS1302_IO = ACC0;           	//相当于汇编中的 RRC 
            DS1302_CLK = 1; 
            DS1302_CLK = 0; 
            ACC = ACC >> 1;  
        }  
    } 
     
    unsigned char DS1302OutputByte(void) 	//实时时钟读取一字节(内部函数) 
    {  
        unsigned char i; 
        for(i=8; i>0; i--) 
        { 
            ACC = ACC >>1;         			//相当于汇编中的 RRC  
            ACC7 = DS1302_IO; 
            DS1302_CLK = 1; 
            DS1302_CLK = 0; 
        }  
        return(ACC);  
    } 
     
    void Write1302(unsigned char ucAddr, unsigned char ucDa)	//ucAddr: DS1302地址, ucData: 要写的数据 
    {   
        DS1302_CLK = 0; 
        DS1302_RST = 1; 
        DS1302InputByte(ucAddr);       	// 地址,命令  
        DS1302InputByte(ucDa);       	// 写1Byte数据 						 
        DS1302_CLK = 1; 
        DS1302_RST = 0;   
    }  
     
    unsigned char Read1302(unsigned char ucAddr)	//读取DS1302某地址的数据 
    { 
        unsigned char ucData;  
        DS1302_CLK = 0; 
        DS1302_RST = 1; 
        DS1302InputByte(ucAddr);        // 地址,命令  
        ucData = DS1302OutputByte();         // 读1Byte数据 
        DS1302_CLK = 1; 
        DS1302_RST = 0; 
        return(ucData); 
    } 
     
    void DS1302_SetProtect(bit flag)        //是否写保护 
    { 
    	if(flag) 
    		Write1302(0x8E,0x80); 
    	else 
    		Write1302(0x8E,0x00); 
    } 
     
    void DS1302_SetTime(unsigned char Address, unsigned char Value)        // 设置时间函数 
    { 
    	DS1302_SetProtect(0);  
    	Write1302(Address, ((Value/10)<<4 | (Value%10)));
    	DS1302_SetProtect(1);	
    } 
     
    void DS1302_GetTime(SYSTEMTIME *Time) 
    { 
    	unsigned char ReadValue; 
    	ReadValue = Read1302(DS1302_SECOND_READ); 
    	Time->Second = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); 
    	ReadValue = Read1302(DS1302_MINUTE_READ); 
    	Time->Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); 
    	ReadValue = Read1302(DS1302_HOUR_READ); 
    	Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); 
    	ReadValue = Read1302(DS1302_DAY_READ); 
    	Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);	 
    	ReadValue = Read1302(DS1302_WEEK_READ); 
    	Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); 
    	ReadValue = Read1302(DS1302_MONTH_READ); 
    	Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); 
    	ReadValue = Read1302(DS1302_YEAR_READ); 
    	Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);	 
    } 
     
    void DateToStr(SYSTEMTIME *Time) 
    { 
    	Time->DateString[0] = Time->Year/10 + '0'; 
    	Time->DateString[1] = Time->Year%10 + '0'; 
    	Time->DateString[2] = '-'; 
    	Time->DateString[3] = Time->Month/10 + '0'; 
    	Time->DateString[4] = Time->Month%10 + '0'; 
    	Time->DateString[5] = '-'; 
    	Time->DateString[6] = Time->Day/10 + '0'; 
    	Time->DateString[7] = Time->Day%10 + '0'; 
    	Time->DateString[8] = '\0'; 
    } 
     
    void TimeToStr(SYSTEMTIME *Time) 
    { 
    	Time->TimeString[0] = Time->Hour/10 + '0'; 
    	Time->TimeString[1] = Time->Hour%10 + '0'; 
    	Time->TimeString[2] = ':'; 
    	Time->TimeString[3] = Time->Minute/10 + '0'; 
    	Time->TimeString[4] = Time->Minute%10 + '0'; 
    	Time->TimeString[5] = ':'; 
    	Time->TimeString[6] = Time->Second/10 + '0'; 
    	Time->TimeString[7] = Time->Second%10 + '0'; 
    	Time->DateString[8] = '\0'; 
    } 
    
    /*  
    void Initial_DS1302(void) 
    { 
    	unsigned char Second=Read1302(DS1302_SECOND); 
    	if(Second&0x80)		   
    		DS1302_SetTime(DS1302_SECOND,0); 
    } 
     
    void BurstWrite1302(unsigned char *pWClock)	//往DS1302写入时钟数据(多字节方式) 
    { 
        unsigned char i; 
        Write1302(0x8e,0x00);         	// 控制命令,WP=0,写操作? 
        DS1302_RST = 0; 
        DS1302_CLK = 0; 
        DS1302_RST = 1; 
        DS1302InputByte(0xbe);        	// 0xbe:时钟多字节写命令 
        for (i = 8; i>0; i--)     		//8Byte = 7Byte 时钟数据 + 1Byte 控制 
        { 
            DS1302InputByte(*pWClock); 	// 写1Byte数据 
            pWClock++; 
        } 
        DS1302_CLK = 1; 
        DS1302_RST = 0; 
    }*/
    /******************************************************************************** 
    void BurstRead1302(unsigned char *pRClock)	//读取DS1302时钟数据(时钟多字节方式) 
    { 
        unsigned char i; 
        DS1302_RST = 0; 
        DS1302_CLK = 0; 
        DS1302_RST = 1; 
        DS1302InputByte(0xbf);             	// 0xbf:时钟多字节读命令  
        for (i=8; i>0; i--)  
        { 
           *pRClock = DS1302OutputByte();   // 读1Byte数据  
           pRClock++; 
        } 
        DS1302_CLK = 1; 
        DS1302_RST = 0; 
    } 
     
    */
    
    void DS1302_TimeStop(bit flag)           // 是否将时钟停止 
    { 
    	unsigned char Data; 
    	Data=Read1302(DS1302_SECOND_READ); 
    	DS1302_SetProtect(0); 
    	if(flag) 
    		Write1302(DS1302_SECOND, Data|0x80); 
    	else 
    		Write1302(DS1302_SECOND, Data&0x7F); 
    	DS1302_SetProtect(1);
    } 
    
    #endif
    [/tag]

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,822
    Yes, we need to see your code as well.

    And stop using [tag][/tag], it doesn't do anything on this board.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  3. #3
    Registered User
    Join Date
    Apr 2011
    Posts
    21
    okay sorry..
    My code is here
    Code:
    #include <at89c51xd2.h>
    #include <string.h>
    #include <ds1302.h>
    
    /*******************************************/
    /* define the button name and the lcd pin  */
    /*******************************************/		
    #define E  			P1_2			//LCD E
    #define RW 			P1_1			//LCD RW
    #define RS 			P1_0			//LCD RS
    #define LCD_DATA		P2			//LCD Data port
    SYSTEMTIME CurrentTime;
    
    /************************************/
    /*    Function that going to use    */
    /************************************/
    void delay(unsigned int);  		    //software delay
    void strobe(void);		            //write in data
    void LCD_init(void);		        //LCD initialisation
    void LCD_print(unsigned char);      //print on LCD
    void LCD_command(unsigned char);  	//to set instruction to cursor
    void LCD_TimerDisp();				//Show time
    
    /*****************/
    /* Main function */
    /*****************/
    void main(void)
    {	
    	unsigned char i;
    
    	for(i=0; i<20; i++)
    		delay(500);
    	
    	LCD_init();
    
    	DS1302_SetTime(DS1302_SECOND, 0);
    	DS1302_SetTime(DS1302_MINUTE, 0);
    	DS1302_SetTime(DS1302_HOUR, 0);
    	DS1302_SetTime(DS1302_WEEK, 1);
    	DS1302_SetTime(DS1302_DAY, 1);
    	DS1302_SetTime(DS1302_MONTH, 1);
    	DS1302_SetTime(DS1302_YEAR, 1);		
    
    	DS1302_TimeStop(0);
    
    	LCD_TimerDisp();
    	
    	while(1);
    
    }
    
    /************/
    /* Function */
    /************/
    
    void delay(unsigned int y)
    {
    	unsigned int x;
    	for(x=0; x<y; x++);
    }
    
    void strobe()		//write in data
    {
    	E = 1;
    	delay(500);
    	E = 0;
    	delay(500);
    }
    
    void LCD_init()		//LCD initialisation
    {
    	RS = 0;
    	RW = 0;
    	LCD_DATA = 0x38;
    	strobe();
    	LCD_DATA = 0x0c;
    	strobe();
    	LCD_DATA = 0x01;
    	strobe();
    	LCD_DATA = 0x06;
    	strobe();
    }
    
    void LCD_print(unsigned char alpha)  //print on LCD
    {
    	RS = 1;     //data type
    	RW = 0;     //write enable
    	LCD_DATA = alpha; //LCD D0 to D7
    	strobe();
    }
    
    void LCD_command(unsigned char beta)	//to set instruction to cursor
    {
    	RS = 0;     //data type
    	RW = 0;     //write enable
    	LCD_DATA = beta;
    	strobe();
    }
    
    void LCD_TimerDisp()
    {
    	unsigned char alphabet;
    	unsigned char nonchar[7];
    	
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		nonchar[alphabet] = ' ';
    	}
    	
    	do
    	{
    	DS1302_GetTime(&CurrentTime);
    	TimeToStr(&CurrentTime);
    	DateToStr(&CurrentTime);
    	LCD_command(0xc0);
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		LCD_print(CurrentTime.TimeString[alphabet]);
    	}
    	LCD_command(0x06);
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		LCD_print(nonchar[alphabet]);
    	}
    	delay(1000);
    	}
    	while(1);
    }

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,822
    Code:
    	unsigned char nonchar[7];
    	
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		nonchar[alphabet] = ' ';
    	}
    Well this loop is an array overrun.

    As are all your other loops in this function.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  5. #5
    Registered User
    Join Date
    Jun 2010
    Location
    Michigan, USA
    Posts
    142
    Quote Originally Posted by Salem View Post
    Well this loop is an array overrun.
    I agree.

    Quote Originally Posted by Salem View Post
    As are all your other loops in this function.
    Should be "As well as the other loop in this function using nonchar.".

    To the original poster:

    I would not call you file in the first message a "header" file. Yes, you may be including it as an "include" file, but since it has function definitions (as opposed to function declarations) in it, I do not consider it a "header" file. <checking> The standard suggests that the both headers and source files can be included. I would consider your file to be a source file that is being included in another source file during the compilation of a single translation unit. This will not cause you a problem until you want to compile two different translation units (only one of which has a int main() ) that both include your source file "ds1302.h" and then need to be linked together.

    Also I would assume that #include <ds1302.h> should be #include "ds1302.h", but that depends on if the file ds1302.h is part of your project or part of a freestanding implementation for which ds1302.h is an implementation defined header file. If it was implementation defined, I think it was poorly designed. And my comment above is why I think that.
    Last edited by pheininger; 05-09-2011 at 07:25 AM. Reason: poor wording

  6. #6
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,676
    For anybody who wants some background on the issue, and to avoid things we've already tried, here are his 3 previous threads on the issue:
    I program DS1302 & AT89C51 C code but LCD display 85 : 85 : 85,what is wrong???
    I got header file for DS1302 on a website, i dont understand the whole code! Help!!
    Im doing c programming on 8051 and DS1302(RTC) to show time on LCD,there a problem..

    Here's a link to the DS1302 data sheet if you don't have it: http://datasheets.maxim-ic.com/en/ds/DS1302.pdf.

    I worry about the timing in DS1302InputByte and DS1302OutputByte functions. The set DS1302_CLK to high, then immediately to low with no delay. If, as your past threads suggest, your uC is running at at least 20MHz, each clock pulse will be 1/20,000,000 or 50ns. That is well below the timing threshold for most signals according to the data sheet. You may need to put a delay in between those two commands.

  7. #7
    Registered User
    Join Date
    Apr 2011
    Posts
    21

    Hi,thanks for replying..

    To salem :
    what you mean by this loop is an array overrun.
    As are all your other loops in this function.

    Well,because the LCD is 2x8,i do that to blank the top line.
    I not sure what you mean by array overrun.
    If not, can you show me the code like how should i change it to?

    Thank you..

    --------------------------------------------------------------------

    TO phrininger :
    you say should be "As well as the other loop in this function using nonchar." well,can you show few examples from changing my code,i need to you know what mean by that. For the #include <ds1302.h> ,the file ds1302.h is part of my project.

    Thank you..

    ---------------------------------------------------------------------

    To anduril462 :
    Thank for mentioning my previous posted threats.

    Well for the DS1302InputByte and DS1302OutputByte functions,im still abit not sure about it.

    It was because i got the ds1302 file from a website,i using this file as a part of my project.

    I am quite a starter and weak programmer. There are still parts of the code which i do not understand and sure of.
    But i have modified quite a lot of that ds1302 file code and also have better understanding of it after reading the ds1302 datasheet.

    well for the DS1302InputByte and DS1302OutputByte functions you say,i should add delay function in between it. Can you show me like the where should i put the delay function at??

    Thank you..

    ----------------------------------------------------------------------
    Last edited by w31q1an9; 05-10-2011 at 10:46 PM.

  8. #8
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,185
    Overrun just means you run over the edge of your array. Your nonchar array has seven characters in it, yet you insist upon trying to write to the eighth position (which doesn't exist).

  9. #9
    Registered User
    Join Date
    Apr 2011
    Posts
    21
    I see,so i should change from unsigned char nonchar[7]; to unsigned char nonchar[6]; ??
    Or can i see an example??
    Well,i don't know what cause the problem that display 85 when run time..
    Is there anything i can do to solve the problem???

  10. #10
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,185
    Quote Originally Posted by w31q1an9 View Post
    I see,so i should change from unsigned char nonchar[7]; to unsigned char nonchar[6]; ??
    If your array is one element too short for what you want to do, making it shorter seems like Not The Thing To Do.
    quzah and Salem like this.

  11. #11
    Registered User
    Join Date
    Apr 2011
    Posts
    21

    okay,thank

    I get what you mean,but i not sure how can i solve this array problem??
    Sorry,i am a starter programmer,quite weak in this.
    Example please,thank.

    I don't think that can be a problem that cause the "85" to appear and moving left & right when the time is running. Am i right?? What can i do to solve that 85 problem?

  12. #12
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by w31q1an9 View Post
    I get what you mean,but i not sure how can i solve this array problem??
    Sorry,i am a starter programmer,quite weak in this.
    Example please,thank.

    I don't think that can be a problem that cause the "85" to appear and moving left & right when the time is running. Am i right?? What can i do to solve that 85 problem?
    Ok, you seem not to understand how arrays work in C...

    If you do... int array[5] ... you get 5 elements, but they are numbered 0, 1, 2, 3 and 4 ... That's 5 elements, count them. C arrays always start at 0 and end at 1 less than the number of elements.

    So that is your boundary, you have to confine your code to array[n] where n can be 0, 1, 2, 3, or 4... outside of those boundaries lies memory you don't own.

    Writing to array[5] could easily trash the return pointer of your function or overwrite another variable causing all kinds of problems *especially* in imbedded systems where the next memory cell might be for a crucial timer or control.

    In all likelyhood your problem is here...
    Code:
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		LCD_print(CurrentTime.TimeString[alphabet]);
    	}
    	LCD_command(0x06);
    	for(alphabet=0; alphabet<8; alphabet++)
    	{
    		LCD_print(nonchar[alphabet]);
    	}
    	delay(1000);
    You defined... char nonchar[7] ... this gives you safe index values of 0,1,2,3,4,5 and 6... your loop is exiting on <8 (i.e. 7) which is one cell out of bounds. You also appear to have the same problem with your currenttime.timestring ... These loops getting out of bounds are probably why you are getting strange characters on your display... you are reading memory you don't own and can't control.
    Salem likes this.

  13. #13
    Registered User
    Join Date
    Apr 2011
    Posts
    21

    Thank man!!

    I have better understanding of the array..
    well the LCD can display 2 rows of 8 characters.
    So " 00:00:00 " , is counted as 8 elements in total which i want to display right??

    for(alphabet=0; alphabet<8; alphabet++) mean that i display 7 characters? Lesser than 8 is from 0 to 7 is total of 7 characters?

    So for my case,im try to display 8 characters for that row.
    So i mean to change for(alphabet=0; alphabet<8; alphabet++) to for(alphabet=0; alphabet<9; alphabet++)?

    sorry,i am still quite confuse,can tell me which i need to change to inorder to correct the problem??

  14. #14
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    Look, if you want to display a scrolling text, and you show 8 characters at a time, and you are just displaying this as a string, and you are not wrapping around:

    [xxxxxxxx]

    There is your LCD. All those xs there are the allowed spots you can see. So your number ("01234567") starts showing up on the display:

    [xxxxxxx0]
    [xxxxxx01]
    ...
    [01234567]
    [1234567x]
    [234567xx]
    ...
    [7xxxxxxx]

    Understand yet? You need to be writing "blank" to some spots (shown here as an x), and you need to be displaying only the visible parts of your number. Note that if you are doing this example as a string, your array needs 9 spaces, because you need the nul character on the end.


    Quzah.
    Hope is the first step on the road to disappointment.

  15. #15
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by w31q1an9 View Post
    I have better understanding of the array..
    well the LCD can display 2 rows of 8 characters.
    So " 00:00:00 " , is counted as 8 elements in total which i want to display right??

    for(alphabet=0; alphabet<8; alphabet++) mean that i display 7 characters? Lesser than 8 is from 0 to 7 is total of 7 characters?

    So for my case,im try to display 8 characters for that row.
    So i mean to change for(alphabet=0; alphabet<8; alphabet++) to for(alphabet=0; alphabet<9; alphabet++)?

    sorry,i am still quite confuse,can tell me which i need to change to inorder to correct the problem??
    You need to change the size of your arrays... not the counters in your loops.

    Think about what I explained...

    You have to display 8 digits... how big does the array have to be? (Hint: the answer is not 7)

    You have an array of 8 digits... how far should you count before exiting your loops?
    (Hint: not 9)

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 6
    Last Post: 05-04-2011, 04:20 PM
  2. Replies: 7
    Last Post: 11-21-2009, 01:02 AM
  3. Converting Zulu Time to Current Time
    By Caldus in forum C++ Programming
    Replies: 3
    Last Post: 06-08-2006, 09:54 PM
  4. Replies: 3
    Last Post: 06-13-2003, 07:47 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21