/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_conf.h"
#include "Display.h"

/* Private definitions -------------------------------------------------------*/
#define I2C_SPEED              100000
#define I2C_SLAVE_ADDRESS7     0xA0

#define DISPLAY_I2CAddress      0x70
#define DISPLAY_InstructionByte 0x00
#define DISPLAY_ControlByte     0x17

#define DISPLAY_SegmentA 0x02
#define DISPLAY_SegmentB 0x01
#define DISPLAY_SegmentC 0x20
#define DISPLAY_SegmentD 0x40
#define DISPLAY_SegmentE 0x80
#define DISPLAY_SegmentF 0x04
#define DISPLAY_SegmentG 0x10
#define DISPLAY_Decimal  0x08

#define DISPLAY_Digit0 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentE | DISPLAY_SegmentF)
#define DISPLAY_Digit1 (DISPLAY_SegmentB | DISPLAY_SegmentC)
#define DISPLAY_Digit2 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentD | DISPLAY_SegmentE | DISPLAY_SegmentG)
#define DISPLAY_Digit3 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentG)
#define DISPLAY_Digit4 (DISPLAY_SegmentB | DISPLAY_SegmentC | DISPLAY_SegmentF | DISPLAY_SegmentG)
#define DISPLAY_Digit5 (DISPLAY_SegmentA | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentF | DISPLAY_SegmentG)
#define DISPLAY_Digit6 (DISPLAY_SegmentA | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentE | DISPLAY_SegmentF | DISPLAY_SegmentG)
#define DISPLAY_Digit7 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentC)
#define DISPLAY_Digit8 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentE | DISPLAY_SegmentF | DISPLAY_SegmentG)
#define DISPLAY_Digit9 (DISPLAY_SegmentA | DISPLAY_SegmentB | DISPLAY_SegmentC | DISPLAY_SegmentD | DISPLAY_SegmentF | DISPLAY_SegmentG)

/* Private variables ---------------------------------------------------------*/
volatile display_State _display_state = DISPLAY_Calibrating;
volatile int32_t       _display_time = 0;
volatile int32_t       _display_alarm = 0;
volatile bool          _display_alarm_enabled = FALSE;

uint8_t _display_digits[] =
{
	DISPLAY_Digit0,
	DISPLAY_Digit1,
	DISPLAY_Digit2,
	DISPLAY_Digit3,
	DISPLAY_Digit4,
	DISPLAY_Digit5,
	DISPLAY_Digit6,
	DISPLAY_Digit7,
	DISPLAY_Digit8,
	DISPLAY_Digit9
};

/* Exported functions --------------------------------------------------------*/

// Display_Init()
// --------------
// Initialize the display module.
extern void Display_Init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED;
	I2C_Init(I2C1, &I2C_InitStructure);

	/* Enable I2C1 -----------------------------------------------------------*/
	I2C_Cmd(I2C1, ENABLE);
}

// Display_Update()
// ----------------
// Send the I2C command to update the display.
extern void Display_Update()
{
	__disable_irq();
	display_State state = _display_state;
	uint32_t time = _display_time;
	uint32_t alarm = _display_alarm;
	bool alarm_enabled = _display_alarm_enabled;
	__enable_irq();

	uint8_t display[4];
	if (state == DISPLAY_Calibrating)
	{
		// display "HI"
		display[0] = 0x00;
		display[1] = 0xB5;
		display[2] = 0x21;
		display[3] = 0x00;
	}
	else
	{
		int32_t displayvalue = time;
		if (state == DISPLAY_SettingAlarm)
		{
			displayvalue = alarm;
		}

		uint8_t hour = (displayvalue / 3600) % 24;
		uint8_t minute = (displayvalue % 3600) / 60;
		uint8_t hour_12 = hour % 12;
		if (hour_12 == 0)
		{
			hour_12 = 12;
		}

		if (hour_12 < 10)
		{
			display[0] = 0x00;
		}
		else
		{
			display[0] = _display_digits[hour_12 / 10];
		}
		display[1] = _display_digits[hour_12 % 10];
		display[2] = _display_digits[minute / 10];
		display[3] = _display_digits[minute % 10];

		display[1] |= DISPLAY_Decimal;
		display[2] |= DISPLAY_Decimal;

		if (hour >= 12)
		{
			display[3] |= DISPLAY_Decimal;
		}

		if (alarm_enabled)
		{
			display[0] |= DISPLAY_Decimal;
		}
	}

	/* Code from AN2824 - I2C optomised examples -----------------------------*/
    /*!< While the bus is busy */
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
    {
    }

    /*!< Send START condition */
    I2C_GenerateSTART(I2C1, ENABLE);

    /*!< Test on EV5 and clear it */
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
    {
    }

    /*!< Send EEPROM address for write */
    I2C_Send7bitAddress(I2C1, DISPLAY_I2CAddress, I2C_Direction_Transmitter);

    /*!< Test on EV6 and clear it */
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
    }

    /*!< Send the EEPROM's internal address to write to : MSB of the address first */
    I2C_SendData(I2C1, DISPLAY_InstructionByte);

    /*!< Test on EV8 and clear it */
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
    }

    /*!< Send the EEPROM's internal address to write to : LSB of the address */
    I2C_SendData(I2C1, DISPLAY_ControlByte);

    /*!< Test on EV8 and clear it */
    while(! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
    }

    /*!< While there is data to be written */
    int NumByteToWrite = 4;
    unsigned char* pBuffer = &(display[0]);
    while(NumByteToWrite--)
    {
        /*!< Send the current byte */
        I2C_SendData(I2C1, *pBuffer); // *pBuffer

        /*!< Point to the next byte to be written */
        pBuffer++;

        /*!< Test on EV8 and clear it */
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        {
        }
    }

    /*!< Send STOP condition */
    I2C_GenerateSTOP(I2C1, ENABLE);
}

// Display_SetState()
// ------------------
// Set the current state of the display.
extern void Display_SetState(display_State state)
{
	_display_state = state;
}

// Display_SetTime()
// -----------------
// Set the time to display when in the Normal or SettingTime state.
extern void Display_SetTime(uint32_t time)
{
    _display_time = time;
}

// Display_SetAlarm()
// ------------------
// Set the value to display when in the SettingAlarm state.
extern void Display_SetAlarm(uint32_t time)
{
	_display_alarm = time;
}

// Display_EnableAlarm()
// ---------------------
// Show or disable the alarm indicator.
extern void Display_EnableAlarm(bool enabled)
{
	_display_alarm_enabled = enabled;
}
