'------------------------------------------------------------------
' DS1307 Library for PIC18 Basic
' BY COS, 2026/03/21, 2024/10, Pic18 Oshonsoft v5.68
' Description: This library handles communication with the
'              DS1307 Real-Time Clock (RTC) module using PIC18.
'              It contains functions for writing, reading, and
'              configuring the clock.
'-------------------------------------------------------------------

' Pin definition for I2C communication with the DS1307
'DS1307_SDA = TRISC.4
'DS1307_SCL = TRISC.3

' I2C protocol constants
'Const ACK = 0                    ' Acknowledge (confirmation)
'Const NACK = 1                   ' No Acknowledge (no confirmation)

' I2C address of the DS1307 (0xD0 is the base address).
Const DS1307_ADDRESS = 0xD0

'---------------------------------------------------------------
' Proc:        DS1307_Init
' Description: Inicializa el módulo DS1307, verifica y reinicia el oscilador.
' Arguments:   Ninguno.
' Returns:     Ninguno.
'---------------------------------------------------------------
Proc DS1307_Init()
    ' Verifica si el oscilador está detenido.
    Dim seconds As Byte
    seconds = DS1307_Read(0x00)  ' Lee el registro de segundos.
    If seconds & 0x80 Then       ' Si el bit 7 está en 1, el oscilador está detenido.
        Call DS1307_Write(0x00, seconds & 0x7f)  ' Reinicia el oscilador si está detenido.
    Endif
    WaitMs 10
End Proc
'---------------------------------------------------------------
' Proc:        DS1307_Set_24h
' Descripción: Configura el DS1307 en modo de 24 horas.
' Argumentos:  Ninguno.
' Retorna:     Ninguno.
'---------------------------------------------------------------
Proc DS1307_Set_24h()
    Dim hour_register As Byte
    Dim hour As Byte
    hour_register = DS1307_Read(0x02)  ' Lee el registro de hora actual.
    hour = hour_register 'Variable temporal para la hora.
    'Borra los bits de control. En el DS1307, el bit 6 es el indicador de modo 12/24 horas.
    hour.6 = 0 'Modo 24 horas.
    hour = Get_BcdToDEC(hour) 'Convierte la hora de BCD a decimal.
    If hour_register.6 = 1 Then 'Si está en modo 12 horas
        If hour_register.5 = 1 Then 'Si es PM
            If hour < 12 Then hour = hour + 12 'Convierte la hora de 12h a 24h.
        Else
            If hour = 12 Then hour = 0 '0h (24h).
        Endif
        Call DS1307_Write(0x02, Get_DecToBCD(hour)) 'Convierte la hora de decimal a BCD y actualiza el DS1307.
    Endif
End Proc
'---------------------------------------------------------------
' Proc:        DS1307_Set_12h
' Descripción: Configura el DS1307 en modo de 12 horas.
' Argumentos:  Ninguno.
' Retorna:     Ninguno.
'---------------------------------------------------------------
Proc DS1307_Set_12h()
    Dim hour_register As Byte
    Dim hour As Byte
    hour_register = DS1307_Read(0x02)  ' Lee el registro de hora actual.
    hour = hour_register
    hour = Get_BcdToDEC(hour_register) 'Convierte el registro de hora de BCD a decimal.
    hour.6 = 0 'Modo de 24 horas (bit 6 del DS1307).
    hour.5 = 0 'Borra el bit de AM/PM en modo de 12h o el bit más significativo en 24h.
    'De 24h a 12h, si ya está en modo de 12h no hace nada.
    If hour_register.6 = 0 Then 'Modo de 24 horas
        If hour >= 12 Then 'Si la hora está entre 12h (PM) y 23h.
            'Convertir a modo de 12h PM.
            'Si la hora = 12, no se modifica.
            If hour > 12 Then hour = hour - 12 'A 12h
            hour = Get_DecToBCD(hour) 'Convierte la hora de decimal a BCD.
            hour.6 = 1 'Inserta el bit que indica el modo de 12 horas (bit 6).
            hour.5 = 1 'Asigna el modo PM.
            Call DS1307_Write(0x02, hour) 'Escribe el registro de hora.
        Else
            If hour = 0 Then hour = 12 '00h a 12h AM
            hour = Get_DecToBCD(hour) 'Convierte la hora de decimal a BCD.
            hour.6 = 1 'Inserta el bit que indica el modo de 12 horas (bit 6).
            hour.5 = 0 'Modo AM.
            Call DS1307_Write(0x02, hour) 'Actualiza el registro de hora.
        Endif
    Endif
End Proc
'---------------------------------------------------------------
' Proc:        DS1307_Set_AM_PM
' Description: Configures the DS1307 in AM/PM mode.
' Arguments:   mode (short) - 0 for AM, 1 for PM.
' Returns:     None
'---------------------------------------------------------------
Const AM_ON = 0 'AM
Const AM_OFF = 1 'PM
Const PM_ON = 1 'PM
Const PM_OFF = 0 'AM
Proc DS1307_Set_AM_PM(mode As Short)
    Dim hour_register As Byte
    Call DS1307_Set_12h()   ' Switch to 12-hour mode
    hour_register = DS1307_Read(0x02)   ' Read the current hour register
    hour_register.5 = mode.0  ' Set the AM/PM bit
    Call DS1307_Write(0x02, hour_register) ' Write the updated hour register
End Proc
'---------------------------------------------------------------
' Function:    DS1307_Get_AM_PM_24h
' Description: Retrieves the AM/PM/24-hour mode from the DS1307.
' Arguments:   None
' Returns:     A Short integer (-1 for 24-hour mode, 0 for AM, 1 for PM)
'---------------------------------------------------------------
Function DS1307_Get_AM_PM_24h() As Short
    Dim hour_register As Byte
    Dim AmPm24h As Short
    AmPm24h = -1   ' Default to 24-hour mode
    hour_register = DS1307_Read(0x02)   ' Read the current hour register
    If hour_register.6 = 1 Then   ' Check if the DS1307 is in 12-hour mode
        If hour_register.5 = 0 Then  ' If bit 5 is 0, it's AM
            AmPm24h = 0  ' Return AM
        Else
            AmPm24h = 1  ' Return PM
        Endif
    Endif
    ReturnValue AmPm24h   ' Return the current mode (AM/PM/24h)
End Function
'---------------------------------------------------------------
' Proc:        DS1307_Write
' Description: Sends a command and a data byte to the DS1307.
' Arguments:   address (Byte) - The address of the register.
'              data (Byte) - The data to be written.
' Returns:     None
'---------------------------------------------------------------
Proc DS1307_Write(address As Byte, data As Byte)
    Start_I2C()             	' Start I2C communication
    Write_I2C(DS1307_ADDRESS) ' DS1307 I2C write address
    Write_I2C(address)      	' Write register address
    Write_I2C(data)         	' Write the data to the register
    Stop_I2C()              	' End I2C communication
End Proc
'---------------------------------------------------------------
' Function:    DS1307_Read
' Description: Reads a byte from the DS1307.
' Arguments:   address (Byte) - The address of the register to read from.
' Returns:     A Byte of data read from the DS1307.
'---------------------------------------------------------------
Function DS1307_Read(address As Byte) As Byte
    Dim data As Byte
    Start_I2C()          	   		' Start I2C communication
    Write_I2C(DS1307_ADDRESS)		' DS1307 I2C write address
    Write_I2C(address)      		' Send the register address to read from
    Start_I2C()             		' Repeated start for reading
    Write_I2C(DS1307_ADDRESS +1)  ' DS1307 I2C Read address
    data = Read_I2C(NACK)   		' Read the data from the DS1307
    Stop_I2C()              		' End I2C communication
    ReturnValue data        		' Return the read data
End Function
'---------------------------------------------------------------
' Proc:        RTC_Set_DateTime
' Description: Sets the date and time in the DS1307.
' 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 DS1307_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 = DS1307_Read(0x02) '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.6 = 1 Then 'If we are in 12h format.
		hr.5 = hour_register.5 'Preserve the previous AM/PM mode.
		hr.6 = 1 '12h mode.
	Endif
	'Write the input data to the DS1307 in BCD format.
    Call DS1307_Write(0x04, Get_DecToBCD(day))
    Call DS1307_Write(0x05, Get_DecToBCD(mth))
    Call DS1307_Write(0x06, Get_DecToBCD(year))
    Call DS1307_Write(0x03, Get_DecToBCD(dow))
    Call DS1307_Write(0x02, hr) 'We already converted the hour to BCD earlier.
    Call DS1307_Write(0x01, Get_DecToBCD(min))
    Call DS1307_Write(0x00, Get_DecToBCD(0))  ' Seconds set to zero.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Get_Date
' Description: Reads the date from the DS1307.
' Arguments:   day, mth, year, dow (all of type Byte by reference (ByRef))
' Returns:     None
'---------------------------------------------------------------
Proc DS1307_Get_Date(ByRef day As Byte, ByRef mth As Byte, ByRef year As Byte, ByRef dow As Byte)
    day = Get_BcdToDEC(DS1307_Read(0x04))  ' Read and convert the day to decimal
    mth = Get_BcdToDEC(DS1307_Read(0x05))  ' Read and convert the month to decimal
    year = Get_BcdToDEC(DS1307_Read(0x06)) ' Read and convert the year to decimal
    dow = Get_BcdToDEC(DS1307_Read(0x03))  ' Read and convert the day of the week to decimal
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Get_Time
' Description: Lee la hora actual desde el DS1307.
' Arguments:   hr, min, sec (todos de tipo Byte por ByRef)
' Returns:     Ninguno
'---------------------------------------------------------------
Proc DS1307_Get_Time(ByRef hr As Byte, ByRef min As Byte, ByRef sec As Byte)
	'Lee el registro de la hora y borra los bit de configuración.
    hr = DS1307_Read(0x02) 'Leer registro de horas (configuración + hora en formato BCD).
	If hr.6 = 1 Then hr.5 = 0 'Si formato 12h borra bit AM/PM
	hr.6 = 0 'Borra formato 12h (en modo 24h no es necesario).
	hr = Get_BcdToDEC(hr) 'Pasa de BCD a decimal
    min = Get_BcdToDEC(DS1307_Read(0x01))  ' Lee y convierte los minutos.
    sec = Get_BcdToDEC(DS1307_Read(0x00))  ' Lee y convierte los segundos.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Set_Hour
' Description: Sets the time in the ds1307.
' 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 DS1307_Set_Hour(hr As Byte)
	Dim hour_register As Byte
	'Read the hour register to insert the previous configuration.
	hour_register = DS1307_Read(0x02) '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.6 = 1 Then 'If we are in 12h format.
		hr.5 = hour_register.5 'Preserve the previous AM/PM mode.
		hr.6 = 1 '12h mode.
	Endif
	'Write the input data to the DS1307 in BCD format.
    Call DS1307_Write(0x02, hr) 'We already converted the hour to BCD earlier.
End Proc
'---------------------------------------------------------------
' Proc:        RTC_Set_Minutes
' Description: Sets the Minutes in the DS1307.
' Arguments:   minutes (type Byte)
' Returns:     None
'---------------------------------------------------------------
Proc DS1307_Set_Minutes(min As Byte)
	'Write the input data to the DS1307 in BCD format.
    Call DS1307_Write(0x01, Get_DecToBCD(min))
End Proc'---------------------------------------------------------------
' Proc:        RTC_Set_Seconds
' Description: Sets the seconds in the DS1307.
' Arguments:   seconds (type Byte)
' Returns:     None
'---------------------------------------------------------------
Proc DS1307_Set_Seconds(sec As Byte)
	'Write the input data to the DS1307 in BCD format.
    Call DS1307_Write(0x00, Get_DecToBCD(sec))
End Proc
' Proc:        DS1307_Set_DayOfWeek
' Description: Sets the day of week in the DS1307.
' Arguments:   dow (day of week) (type Byte, value 1 to 7)
' Returns:     None
'---------------------------------------------------------------
Proc DS1307_Set_DayOfWeek(dow As Byte)
	'Write the input data to the DS1302 in BCD format.
	Call DS1307_Write(0x03, Get_DecToBCD(dow))
End Proc
' Proc:        DS1307_Get_DayOfWeek
' Description: Reads the day of week in the DS1302.
' Arguments:   none
' Returns:     day of week (type byte, value 1 to 7)
'---------------------------------------------------------------
Function DS1307_Get_DayOfWeek() As Byte
	'Read the data to the DS1302 in BCD format.
    ReturnValue Get_BcdToDEC(DS1307_Read(0x03))  ' Read and convert the day of the week to decimal.
End Function
'---------------------------------------------------------------
' Proc:        DS1307_SquareWave
' Description: Enables the square wave output on the DS1307.
'              This function configures the control register to
'              activate the square wave signal on the SQW/OUT pin.
' Arguments:   freq (Byte) - The frequency of the square wave:
'                 0: 1Hz
'                 1: 4.096kHz
'                 2: 8.192kHz
'                 3: 32.768kHz
' Returns:     None
'---------------------------------------------------------------
Const DS1307_1hz = 		%00010000
Const DS1307_4096hz =	%00010001
Const DS1307_8192hz = 	%00010010
Const DS1307_32768hz =	%00010011
Const DS1307_DISABLE = 	%00000000
Proc DS1307_SquareWave(freq As Byte)
    ' Write the updated control register to the DS1307
    Call DS1307_Write(0x07, freq)
End Proc
'---------------------------------------------------------------
' Proc:        DS1307_Write_RAM
' Descripción: Escribe un byte en la memoria RAM volatil del DS1307.
' Argumentos:  address (Byte) - Dirección donde escribir (0 a 55).
'              data (Byte) - Dato a escribir.
' Retorna:     Ninguno.
'---------------------------------------------------------------
Proc DS1307_Write_RAM(address As Byte, data As Byte)
    If address > 55 Then address = 55 'Rango 0 a 55, porque la RAM volatil del DS1307 es de 56 bytes.
    address = address + 0x08  ' Las direcciones de RAM comienzan en 0x08.
    Call DS1307_Write(address, data)  ' Escribe el dato en la RAM.
End Proc
'---------------------------------------------------------------
' Function:    DS1307_Read_RAM
' Descripción: Lee un byte de la memoria RAM del DS1307.
' Argumentos:  address (Byte) - Dirección de donde leer (0 a 55).
' Retorna:     El valor leído de la RAM.
'---------------------------------------------------------------
Function DS1307_Read_RAM(address As Byte) As Byte
    If address > 55 Then address = 55 'Rango 0 a 55, límite de la RAM del DS1307.
    address = address + 0x08  ' Las direcciones de RAM comienzan en 0x08.
    ReturnValue DS1307_Read(address)  ' Lee el valor desde la RAM.
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