'------------------------------------------------------------------
' DS1302 Library for PIC18 Basic
' BY COS, 2024/09/12, Pic18 Oshonsoft v5.64
' Description: This library handles communication with the
'              DS1302 Real-Time Clock (RTC) module using PIC18.
'              It contains functions for writing, reading, and
'              configuring the clock and NVRam memory of the DS1302.
'-------------------------------------------------------------------
' Pin definition for connection to the DS1302
Symbol RTC_SCLK = LATC.3
Symbol RTC_DATA  = LATC.4
Symbol RTC_DATA_INPUT = PORTC.4
Symbol RTC_RST = LATC.0
Symbol RTC_SCLK_IO = TRISC.3
Symbol RTC_DATA_IO = TRISC.4
Symbol RTC_RST_IO = TRISC.0
'---------------------------------------------------------------
' Function:    RTC_DATA_IN
' Description: Reads the state of the DS1302 DATA I/O pin.
' Arguments:   None
' Returns:     A Bit value (state of the DATA I/O pin).
'---------------------------------------------------------------
Function RTC_DATA_IN() As Bit
    RTC_DATA_IO = 1   	' Set the DATA I/O pin as input.
    WaitUs 1         	' Wait for 1 microsecond for stabilization.
    ReturnValue RTC_DATA_INPUT  ' Return the current state of the DATA I/O pin.
    RTC_DATA_IO = 0   	' Reconfigure the DATA I/O pin as output.
End Function
'---------------------------------------------------------------
' Proc:        Init_DS1302
' Description: Initializes the DS1302 module, unlocks the registers,
'              and configures the oscillator.
' Arguments:   None
' Returns:     None
'---------------------------------------------------------------
Proc Init_DS1302()
    ' Configure DS1302 pins as outputs.
    RTC_SCLK_IO = 0    	' Set the SCL pin as output.
    RTC_DATA_IO = 0    	' Set the DATA I/O pin as output.
    RTC_RST_IO = 0    	' Set the RST pin as output.
    ' Ensure DS1302 is inactive.
    Low RTC_RST       	' Desactivate the RST pin.
    WaitUs 2          	' Wait for 2 microseconds.
    Low RTC_SCLK       	' Ensure the clock is low.
    ' Unlock the write control.
    Call Write_DS1302(0x8E, 0) ' Unlock writing to the DS1302.
    ' Configure the oscillator (trickle charger).
    Call Write_DS1302(0x90, 0xA4)
    ' Check if the oscillator is stopped.
    Dim seconds As Byte
    seconds = Read_DS1302(0x81)  	' Read the seconds register.
    If seconds & 0x80 Then       	' If bit 7 is 1, the oscillator is stopped.
        Call Write_DS1302(0x80, 0)  ' Restart the oscillator if it's stopped.
    Endif
End Proc
'---------------------------------------------------------------
' Proc:        Set_DS1302_24h
' Description: Configures the DS1302 in 24-hour mode.
' Arguments:   None.
' Returns:     None.
'---------------------------------------------------------------
Proc Set_DS1302_24h()
    Dim hour_register As Byte
	Dim hour As Byte
    hour_register = Read_DS1302(0x85)  ' Read the current hour register.
	hour = hour_register 'Temporary variable for the hour.
	'Clear control bits.
	hour.7 = 0
	hour.5 = 0
	hour = Get_BcdToDEC(hour) 		'Convert the hour from BCD to decimal.
	If hour_register.7 = 1 Then 	'If 12h
		If hour_register.5 = 1 Then 'If PM
			If hour < 12 Then hour = hour + 12 'Convert the hour from 12h to 24h.
		Else
			If hour = 12 Then hour = 0 '0h (24h).
		Endif
		Call Write_DS1302(0x84, Get_DecToBCD(hour)) 'Convert the hour from decimal to BCD and update DS1302.
	Endif
End Proc
'---------------------------------------------------------------
' Proc:        Set_DS1302_12h
' Description: Configures the DS1302 in 12-hour mode.
' Arguments:   None.
' Returns:     None.
'---------------------------------------------------------------
Proc Set_DS1302_12h()
    Dim hour_register As Byte
	Dim hour As Byte
    hour_register = Read_DS1302(0x85)  ' Read the current hour register.
    hour = hour_register
	hour = Get_BcdToDEC(hour_register) 'Convert the hour register from BCD to decimal.
	hour.7 = 0 '24h mode.
	hour.5 = 0 'Clear AM/PM bit in 12h mode or most significant hour bit (BCD) in 24h mode.
	'From 24h to 12h, if already in 12h mode do nothing.
	If hour_register.7 = 0 Then '24h mode
		If hour >= 12 Then 'If the hour is between 12h (PM) and 23h.
			'Convert to 12h PM mode.
			'If hour = 12 it is not modified.
			If hour > 12 Then hour = hour - 12 'To 12h
			hour = Get_DecToBCD(hour) 'Convert the hour from decimal to BCD.
			hour.7 = 1 'Insert the bit that indicates 12h mode.
			hour.5 = 1 'Assign PM mode.
			Call Write_DS1302(0x84, hour) 'Write the hour register.
		Else
			If hour = 0 Then hour = 12 '00h to 12h AM
			hour = Get_DecToBCD(hour) 'Convert the hour from decimal to BCD.
			hour.7 = 1 'Insert the bit that indicates 12h mode.
			hour.5 = 0 'AM mode.
			Call Write_DS1302(0x84, hour) 'Update the hour register.
		Endif
	Endif
End Proc
'---------------------------------------------------------------
' Proc:        Set_DS1302_AM_PM
' Description: Configures the DS1302 in AM/PM mode.
' Arguments:   mode (short) - 0 for AM, 1 for PM.
' Returns:     None
'---------------------------------------------------------------
Const AM_ON = 0 'AM
Const PM_ON = 1 'PM
Proc Set_DS1302_AM_PM(mode As Short)
	If mode <> 0 Then mode = 1 'If it's not AM it's PM.
	Call Set_DS1302_12h() 'Switch to 12h format.
    Dim hour_register As Byte
    hour_register = Read_DS1302(0x85) ' Read the current hour register.
 	hour_register.5 = mode.0 ' Set bit 5 for AM/PM hours.
	Call Write_DS1302(0x84, hour_register) ' Write the hour register.
End Proc
'---------------------------------------------------------------
' Function:    Get_DS1302_AM_PM_24h
' Description: Gets the AM/PM/24h mode from the DS1302.
' Arguments:   None
' Returns:     Returns an integer with the sign (0 AM, 1 PM, and -1 for 24h).
'---------------------------------------------------------------
Function Get_DS1302_AM_PM_24h() As Short
    Dim hour_register As Byte
	Dim AmPm24h As Short 'Temporary
    AmPm24h = -1 'Default to 24h mode.
    hour_register = Read_DS1302(0x85) ' Read the current hour register.
    If hour_register.7 = 1 Then 'If in 12h format.
		If hour_register.5 = 0 Then 'If in AM mode.
			AmPm24h = 0 'AM
		Else
			AmPm24h = 1 'PM
		Endif
	Endif
    ReturnValue AmPm24h 'Return the state.
End Function
'---------------------------------------------------------------
' Proc:        Write_DS1302_Byte
' Description: Sends a byte to the DS1302 bit by bit.
' Arguments:   byte_value (Byte) - Byte to send.
' Returns:     None
'---------------------------------------------------------------
Proc Write_DS1302_Byte(byte_value As Byte)
    Dim i As Byte
    ' Send the byte bit by bit (8 bits).
    For i = 0 To 7
        RTC_DATA = byte_value.0    ' Send the least significant bit.
        ' Generate a clock pulse.
        High RTC_SCLK
        Low RTC_SCLK
        ' Shift the byte to the right to send the next bit.
        byte_value = ShiftRight(byte_value, 1)
    Next i
End Proc
'---------------------------------------------------------------
' Proc:        Write_DS1302
' Description: Sends a command and a data byte to the DS1302.
' Arguments:   address (Byte) - Command or address.
'              data (Byte) - Data to send.
' Returns:     None
'---------------------------------------------------------------
Proc Write_DS1302(address As Byte, data As Byte)
   High RTC_RST               ' Activate the RST pin to start communication.
   Call Write_DS1302_Byte(address)  ' Send the command.
   Call Write_DS1302_Byte(data) ' Send the data.
   Low RTC_RST                ' Deactivate the RST pin to end communication.
End Proc
'---------------------------------------------------------------
' Function:    Read_DS1302
' Description: Reads a byte from the DS1302.
' Arguments:   address (Address to read from in the DS1302).
' Returns:     A byte of data read from the DS1302.
'---------------------------------------------------------------
Function Read_DS1302(address As Byte) As Byte
    Dim i, data As Byte  ' Local variables.
    High RTC_RST         ' Activate the RTC_RST pin to start communication.
    WaitUs 1             ' Wait before starting the read.
    Call Write_DS1302_Byte(address)  ' Send the address.
    ' Loop to read the 8 bits from the DS1302.
    For i = 0 To 7
        data = data >> 1         ' Shift the data to the right.
        data.7 = RTC_DATA_IN()    ' Read the bit from DATA I/O.
        High RTC_SCLK             ' Clock pulse.
        WaitUs 2
        Low RTC_SCLK
        WaitUs 2
    Next i
    Low RTC_RST         ' End communication.
    ReturnValue data    ' Return the read value.
End Function
'---------------------------------------------------------------
' Proc:        RTC_Set_DateTime
' Description: Sets the date and time in the DS1302.
' Arguments:   day, mth, year, dow, hr, min (all of type Byte)
' Returns:     None
'---------------------------------------------------------------
'Before using, check first if we are in 12h or 24h mode.
'If we store the hour in 12h format while in 24h mode, the AM/PM may not be correct.
Proc RTC_Set_DateTime(day As Byte, mth As Byte, year As Byte, dow As Byte, hr As Byte, min As Byte)
	Dim hour_register As Byte
	'Read the hour register to insert the previous configuration.
	hour_register = Read_DS1302(0x85) 'Read the hour register (configuration + BCD hour).
	hr = Get_DecToBCD(hr) 'Convert the hour to BCD.
	'Insert the previous configuration bits into the hour.
	If hour_register.7 = 1 Then 'If we are in 12h format.
		hr.5 = hour_register.5 'Preserve the previous AM/PM mode.
		hr.7 = 1 '12h mode.
	Endif
	'Write the input data to the DS1302 in BCD format.
    Call Write_DS1302(0x86, Get_DecToBCD(day))
    Call Write_DS1302(0x88, Get_DecToBCD(mth))
    Call Write_DS1302(0x8C, Get_DecToBCD(year))
    Call Write_DS1302(0x8A, Get_DecToBCD(dow))
    Call Write_DS1302(0x84, hr) 'We already converted the hour to BCD earlier.
    Call Write_DS1302(0x82, Get_DecToBCD(min))
    Call Write_DS1302(0x80, Get_DecToBCD(0))  ' Seconds set to zero.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Get_Date
' Description: Reads the date from the DS1302.
' Arguments:   day, mth, year, dow (all of type Byte by reference (ByRef))
' Returns:     None
'---------------------------------------------------------------
Proc RTC_Get_Date(ByRef day As Byte, ByRef mth As Byte, ByRef year As Byte, ByRef dow As Byte)
    day = Get_BcdToDEC(Read_DS1302(0x87))  ' Read and convert the day to decimal.
    mth = Get_BcdToDEC(Read_DS1302(0x89))  ' Read and convert the month to decimal.
    year = Get_BcdToDEC(Read_DS1302(0x8D)) ' Read and convert the year to decimal.
    dow = Get_BcdToDEC(Read_DS1302(0x8B))  ' Read and convert the day of the week to decimal.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Get_Time
' Description: Reads the current time from the DS1302.
' Arguments:   hr, min, sec (all of type Byte by ByRef)
' Returns:     None
'---------------------------------------------------------------
Proc RTC_Get_Time(ByRef hr As Byte, ByRef min As Byte, ByRef sec As Byte)
	'Read the hour register and clear the configuration bits.
    hr = Read_DS1302(0x85) 'Read hour register (configuration + BCD hour).
	If hr.7 = 1 Then hr.5 = 0 'If 12h format, clear AM/PM bit.
	hr.7 = 0 'Clear 12h format bit (not necessary in 24h mode).
	hr = Get_BcdToDEC(hr) 'Convert from BCD to decimal.
    min = Get_BcdToDEC(Read_DS1302(0x83))  ' Read and convert the minutes.
    sec = Get_BcdToDEC(Read_DS1302(0x81))  ' Read and convert the seconds.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Set_Hour
' Description: Sets the time in the ds1302.
' Arguments:   hout (Byte)
' Returns:     None
'---------------------------------------------------------------
'Before using, check first if we are in 12h or 24h mode.
'If we store the hour in 12h format while in 24h mode, the AM/PM may not be correct.
Proc RTC_Set_Hour(hr As Byte)
	Dim hour_register As Byte
	'Read the hour register to insert the previous configuration.
	hour_register = Read_DS1302(0x85) 'Read the hour register (configuration + BCD hour).
	hr = Get_DecToBCD(hr) 'Convert the hour to BCD.
	'Insert the previous configuration bits into the hour.
	If hour_register.7 = 1 Then 'If we are in 12h format.
		hr.5 = hour_register.5 'Preserve the previous AM/PM mode.
		hr.7 = 1 '12h mode.
	Endif
	'Write the input data to the DS1302 in BCD format.
    Call Write_DS1302(0x84, hr) 'We already converted the hour to BCD earlier.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Set_Minutes
' Description: Sets the Minutes in the DS1302.
' Arguments:   minutes (type Byte)
' Returns:     None
'---------------------------------------------------------------
Proc RTC_Set_Minutes(min As Byte)
	'Write the input data to the DS1302 in BCD format.
    Call Write_DS1302(0x82, Get_DecToBCD(min))
End Proc'---------------------------------------------------------------
' Proc:        RTC_Set_Seconds
' Description: Sets the seconds in the DS1302.
' Arguments:   seconds (type Byte)
' Returns:     None
'---------------------------------------------------------------
Proc RTC_Set_Seconds(sec As Byte)
	'Write the input data to the DS1302 in BCD format.
    Call Write_DS1302(0x80, Get_DecToBCD(sec))
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Write_NVRam
' Description: Writes a byte to the non-volatile memory (NVRam) of the DS1302.
' Arguments:   address (Byte) - Address to write to (0 to 30).
'              data (Byte) - Data to write.
' Returns:     None
'---------------------------------------------------------------
Proc RTC_Write_NVRam(address As Byte, data As Byte)
	If address > 30 Then address = 30 'Range 0 to 30.
	'To write %11address0, address length is 5 bits.
	address = address << 1 'Adapt to register format.
    Call Write_DS1302(address | 0xC0, data)  ' Write the data to the NVRam.
End Proc
'---------------------------------------------------------------
' Function:    RTC_Read_NVRam
' Description: Reads a byte from the non-volatile memory (NVRam) of the DS1302.
' Arguments:   address (Byte) - Address to read from (0 to 30).
' Returns:     The value read from the NVRam.
'---------------------------------------------------------------
Function RTC_Read_NVRam(address As Byte) As Byte
	If address > 30 Then address = 30 'Range 0 to 30.
	'To read %11address1, address length is 5 bits.
	address = address << 1 'Adapt to register format.
    ReturnValue Read_DS1302(address | 0xC1)  ' Read the value from NVRam.
End Function
'---------------------------------------------------------------
' Function:    Get_DecToBCD
' Description: Converts a decimal value to its BCD format.
' Arguments:   data (Byte) - Decimal value to convert.
' Returns:     The value converted to BCD.
'---------------------------------------------------------------
Function Get_DecToBCD(data As Byte) As Byte
    Dim nibh, nibl As Byte
    nibh = data / 10              ' Calculate the tens.
    nibl = data - (nibh * 10)     ' Calculate the units.
    ReturnValue (nibh << 4) | nibl  ' Return the value in BCD format.
End Function
'---------------------------------------------------------------
' Function:    Get_BcdToDEC
' Description: Converts a BCD value to decimal.
' Arguments:   data (Byte) - BCD value to convert.
' Returns:     The value converted to decimal.
'---------------------------------------------------------------
Function Get_BcdToDEC(data As Byte) As Byte
    Dim i As Byte
    i = data
    data = (i >> 4) * 10          ' Extract the tens.
    data = data + (i & 0x0F)      ' Extract the units.
    ReturnValue data              ' Return the decimal value.
End Function