/******************************************************************************
 * TM4C129ENCPDT + LSM6DSV16X + SSD1306 OLED
 * FULLY CORRECTED - Proper sensor initialization for LSM6DSV16X   kumar
 ******************************************************************************/

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/i2c.h"
#include "driverlib/uart.h"

/* =========================================================
   OLED
========================================================= */
#define OLED_ADDR              0x3C
#define OLED_WIDTH             128
#define OLED_HEIGHT            64
#define OLED_PAGES             (OLED_HEIGHT / 8)

/* =========================================================
   LSM6DSV16X - CORRECT REGISTER VALUES FROM DATASHEET
========================================================= */
#define LSM6DSV16X_ADDR        0x6A
#define WHO_AM_I_REG           0x0F
#define WHO_AM_I_VALUE         0x70

/* Control registers - CORRECT for LSM6DSV16X */
#define CTRL1_XL               0x10
#define CTRL2_G                0x11
#define CTRL3_C                0x12
#define CTRL4_C                0x13
#define CTRL5_C                0x14
#define CTRL6_C                0x15
#define CTRL7_G                0x16
#define CTRL8_XL               0x17
#define CTRL9_XL               0x18
#define CTRL10_C               0x19

/* Output registers */
#define OUT_TEMP_L             0x20
#define OUT_TEMP_H             0x21
#define OUTX_L_G               0x22
#define OUTX_H_G               0x23
#define OUTY_L_G               0x24
#define OUTY_H_G               0x25
#define OUTZ_L_G               0x26
#define OUTZ_H_G               0x27
#define OUTX_L_A               0x28
#define OUTX_H_A               0x29
#define OUTY_L_A               0x2A
#define OUTY_H_A               0x2B
#define OUTZ_L_A               0x2C
#define OUTZ_H_A               0x2D

/* Configuration values - CORRECT for LSM6DSV16X */
/* For CTRL1_XL: ODR=104Hz (0x30) + FS=±4g (0x04) = 0x34 */
/* For CTRL2_G:  ODR=104Hz (0x30) + FS=±1000dps (0x0C) = 0x3C */
#define XL_ODR_104Hz           0x30
#define XL_FS_4g               0x04
#define G_ODR_104Hz            0x30
#define G_FS_1000dps           0x0C

#define I2C_RETRY_COUNT        5

uint32_t g_ui32SysClock;
uint8_t g_sensor_addr = 0;

/* Frame buffer */
uint8_t oled_buffer[OLED_WIDTH * OLED_PAGES];
uint8_t g_font_scale = 1;

/* Sensor data - using int16_t for raw values */
typedef struct {
    int16_t ax, ay, az;
    int16_t gx, gy, gz;
    int16_t temperature;
} sensor_raw_t;
sensor_raw_t sensor_raw;

/* Function prototypes */
void OLED_Clear(void);
void OLED_Update(void);
void OLED_Command(uint8_t cmd);
void OLED_SetCursor(uint8_t col, uint8_t page);
void OLED_DrawPixel(uint8_t x, uint8_t y, bool color);
void OLED_DrawChar(uint8_t x, uint8_t y, char ch);
void OLED_DrawString(uint8_t x, uint8_t y, char *str);
void OLED_TestPattern(void);
void Display_Data(bool sensor_ok);

/* =========================================================
   8x8 Font (same as your existing)
========================================================= */
const uint8_t Font8x8[][8] = {
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x18,0x3C,0x3C,0x18,0x18,0x00},
    {0x00,0x66,0x66,0x24,0x00,0x00,0x00,0x00},
    {0x00,0x36,0x36,0x7F,0x36,0x36,0x7F,0x36},
    {0x08,0x1C,0x2A,0x2A,0x08,0x1C,0x2A,0x2A},
    {0x00,0x62,0x64,0x08,0x10,0x20,0x46,0x86},
    {0x00,0x1C,0x22,0x22,0x14,0x2A,0x4A,0x44},
    {0x00,0x18,0x18,0x08,0x00,0x00,0x00,0x00},
    {0x00,0x0C,0x18,0x30,0x30,0x30,0x18,0x0C},
    {0x00,0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30},
    {0x00,0x00,0x18,0x5A,0x3C,0x5A,0x18,0x00},
    {0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18},
    {0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18},
    {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40},
    {0x3E,0x63,0x73,0x7B,0x6F,0x67,0x3E,0x00},
    {0x0C,0x0E,0x0C,0x0C,0x0C,0x0C,0x3F,0x00},
    {0x1E,0x33,0x30,0x1C,0x06,0x33,0x3F,0x00},
    {0x1E,0x33,0x30,0x1C,0x30,0x33,0x1E,0x00},
    {0x38,0x3C,0x36,0x33,0x7F,0x30,0x78,0x00},
    {0x3F,0x03,0x1F,0x30,0x30,0x33,0x1E,0x00},
    {0x1C,0x06,0x03,0x1F,0x33,0x33,0x1E,0x00},
    {0x3F,0x33,0x30,0x18,0x0C,0x0C,0x0C,0x00},
    {0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00},
    {0x1E,0x33,0x33,0x3E,0x30,0x18,0x0E,0x00},
    {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00},
    {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x08},
    {0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10},
    {0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00},
    {0x00,0x20,0x10,0x08,0x04,0x02,0x04,0x08},
    {0x1E,0x33,0x30,0x1C,0x0C,0x00,0x0C,0x00},
    {0x3C,0x4A,0x56,0x52,0x52,0x4E,0x3C,0x00},
    {0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00},
    {0x3F,0x66,0x66,0x3E,0x66,0x66,0x3F,0x00},
    {0x3C,0x66,0x03,0x03,0x03,0x66,0x3C,0x00},
    {0x1F,0x36,0x66,0x66,0x66,0x36,0x1F,0x00},
    {0x7F,0x46,0x16,0x1E,0x16,0x46,0x7F,0x00},
    {0x7F,0x46,0x16,0x1E,0x16,0x06,0x0F,0x00},
    {0x3C,0x66,0x03,0x03,0x73,0x66,0x7C,0x00},
    {0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00},
    {0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},
    {0x78,0x30,0x30,0x30,0x33,0x33,0x1E,0x00},
    {0x67,0x66,0x36,0x1E,0x36,0x66,0x67,0x00},
    {0x0F,0x06,0x06,0x06,0x46,0x66,0x7F,0x00},
    {0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x00},
    {0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x00},
    {0x1C,0x36,0x63,0x63,0x63,0x36,0x1C,0x00},
    {0x3F,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00},
    {0x1E,0x33,0x33,0x33,0x3B,0x1E,0x38,0x00},
    {0x3F,0x66,0x66,0x3E,0x36,0x66,0x67,0x00},
    {0x1E,0x33,0x07,0x0E,0x38,0x33,0x1E,0x00},
    {0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},
    {0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00},
    {0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00},
    {0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00},
    {0x63,0x36,0x1C,0x1C,0x36,0x63,0x63,0x00},
    {0x33,0x33,0x1E,0x0C,0x0C,0x0C,0x1E,0x00},
    {0x7F,0x63,0x31,0x18,0x4C,0x66,0x7F,0x00},
    {0x3C,0x20,0x20,0x20,0x20,0x20,0x3C,0x00},
    {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01},
    {0x3C,0x04,0x04,0x04,0x04,0x04,0x3C,0x00},
    {0x08,0x14,0x22,0x41,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00},
    {0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x1C,0x22,0x22,0x22,0x3C,0x00},
    {0x00,0x20,0x20,0x3C,0x22,0x22,0x3C,0x00},
    {0x00,0x00,0x1C,0x20,0x20,0x20,0x1C,0x00},
    {0x00,0x04,0x04,0x1C,0x24,0x24,0x1C,0x00},
    {0x00,0x00,0x1C,0x22,0x3E,0x20,0x1C,0x00},
    {0x00,0x0E,0x10,0x10,0x7C,0x10,0x10,0x00},
    {0x00,0x00,0x1C,0x22,0x22,0x1C,0x02,0x3C},
    {0x00,0x20,0x20,0x3C,0x22,0x22,0x22,0x00},
    {0x00,0x08,0x00,0x18,0x08,0x08,0x1C,0x00},
    {0x00,0x04,0x00,0x0C,0x04,0x24,0x24,0x18},
    {0x00,0x20,0x20,0x24,0x28,0x30,0x2C,0x00},
    {0x00,0x18,0x08,0x08,0x08,0x08,0x1C,0x00},
    {0x00,0x00,0x00,0x36,0x49,0x49,0x49,0x00},
    {0x00,0x00,0x00,0x3C,0x22,0x22,0x22,0x00},
    {0x00,0x00,0x00,0x1C,0x22,0x22,0x1C,0x00},
    {0x00,0x00,0x3C,0x22,0x22,0x3C,0x20,0x20},
    {0x00,0x00,0x1C,0x24,0x24,0x1C,0x04,0x06},
    {0x00,0x00,0x00,0x2C,0x30,0x20,0x20,0x00},
    {0x00,0x00,0x1C,0x20,0x1C,0x04,0x38,0x00},
    {0x00,0x10,0x10,0x7C,0x10,0x10,0x0C,0x00},
    {0x00,0x00,0x00,0x22,0x22,0x22,0x1C,0x00},
    {0x00,0x00,0x00,0x22,0x22,0x14,0x08,0x00},
    {0x00,0x00,0x00,0x49,0x49,0x49,0x36,0x00},
    {0x00,0x00,0x00,0x22,0x14,0x08,0x14,0x22},
    {0x00,0x00,0x22,0x22,0x22,0x1C,0x08,0x30},
    {0x00,0x00,0x00,0x3E,0x08,0x10,0x3E,0x00},
};

const uint8_t* Font8x8_GetBitmap(char ch) {
    if (ch < 32 || ch > 126) ch = ' ';
    return Font8x8[ch - 32];
}

/* =========================================================
   Delay and UART
========================================================= */
void DelayUs(uint32_t us) {
    SysCtlDelay((g_ui32SysClock / 3 / 1000000) * us);
}

void DelayMs(uint32_t ms) {
    SysCtlDelay((g_ui32SysClock / 3 / 1000) * ms);
}

void UART0_Init(void) {
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0));
    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinConfigure(GPIO_PA1_U0TX);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTConfigSetExpClk(UART0_BASE, g_ui32SysClock, 115200,
                        UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE);
    UARTEnable(UART0_BASE);
}

void UART_Print(char *str) {
    while(*str) UARTCharPut(UART0_BASE, *str++);
}

/* =========================================================
   I2C Functions
========================================================= */
void I2C0_Init(void) {
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));
    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
    I2CMasterInitExpClk(I2C0_BASE, g_ui32SysClock, false);
    DelayMs(10);
}

bool I2C0_Write(uint8_t addr, uint8_t *data, uint32_t len) {
    uint32_t i;
    uint32_t timeout;

    I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);

    for(i = 0; i < len; i++) {
        timeout = 10000;
        while(I2CMasterBusy(I2C0_BASE) && timeout--) DelayUs(1);
        if(timeout == 0) return false;

        I2CMasterDataPut(I2C0_BASE, data[i]);

        if(len == 1) {
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
        } else if(i == 0) {
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        } else if(i == len-1) {
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        } else {
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_CONT);
        }

        timeout = 10000;
        while(I2CMasterBusy(I2C0_BASE) && timeout--) DelayUs(1);
        if(timeout == 0) return false;
        if(I2CMasterErr(I2C0_BASE)) return false;
        if(i < len-1) DelayUs(10);
    }
    return true;
}

void I2C1_Init(void) {
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C1);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C1));
    GPIOPinConfigure(GPIO_PG0_I2C1SCL);
    GPIOPinConfigure(GPIO_PG1_I2C1SDA);
    GPIOPinTypeI2CSCL(GPIO_PORTG_BASE, GPIO_PIN_0);
    GPIOPinTypeI2C(GPIO_PORTG_BASE, GPIO_PIN_1);
    I2CMasterInitExpClk(I2C1_BASE, g_ui32SysClock, false);
}

bool I2C1_Write(uint8_t addr, uint8_t reg, uint8_t data) {
    uint32_t timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;

    I2CMasterSlaveAddrSet(I2C1_BASE, addr, false);
    I2CMasterDataPut(I2C1_BASE, reg);
    I2CMasterControl(I2C1_BASE, I2C_MASTER_CMD_BURST_SEND_START);

    timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;
    if(I2CMasterErr(I2C1_BASE)) return false;

    I2CMasterDataPut(I2C1_BASE, data);
    I2CMasterControl(I2C1_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);

    timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;
    return (I2CMasterErr(I2C1_BASE) == 0);
}

bool I2C1_Read(uint8_t addr, uint8_t reg, uint8_t *data) {
    uint32_t timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;

    I2CMasterSlaveAddrSet(I2C1_BASE, addr, false);
    I2CMasterDataPut(I2C1_BASE, reg);
    I2CMasterControl(I2C1_BASE, I2C_MASTER_CMD_SINGLE_SEND);

    timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;
    if(I2CMasterErr(I2C1_BASE)) return false;

    I2CMasterSlaveAddrSet(I2C1_BASE, addr, true);
    I2CMasterControl(I2C1_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

    timeout = 10000;
    while(I2CMasterBusy(I2C1_BASE) && timeout--) DelayUs(1);
    if(timeout == 0) return false;
    if(I2CMasterErr(I2C1_BASE)) return false;

    *data = I2CMasterDataGet(I2C1_BASE);
    return true;
}

bool I2C1_Write_Retry(uint8_t addr, uint8_t reg, uint8_t data) {
    uint8_t attempt;
    for(attempt = 0; attempt < I2C_RETRY_COUNT; attempt++) {
        if(I2C1_Write(addr, reg, data)) return true;
        DelayMs(5);
    }
    return false;
}

bool I2C1_Read_Retry(uint8_t addr, uint8_t reg, uint8_t *data) {
    uint8_t attempt;
    for(attempt = 0; attempt < I2C_RETRY_COUNT; attempt++) {
        if(I2C1_Read(addr, reg, data)) return true;
        DelayMs(5);
    }
    return false;
}

/* =========================================================
   Sensor Functions - CORRECTED
========================================================= */
void Sensor_Probe(void) {
    uint8_t whoami;
    char msg[64];

    UART_Print("Probing LSM6DSV16X...\r\n");

    if(I2C1_Read_Retry(0x6A, WHO_AM_I_REG, &whoami)) {
        sprintf(msg, "  0x6A: WHO_AM_I = 0x%02X\r\n", whoami);
        UART_Print(msg);
        if(whoami == WHO_AM_I_VALUE) {
            g_sensor_addr = 0x6A;
            UART_Print(">>> Sensor found at 0x6A!\r\n");
            return;
        }
    }

    if(I2C1_Read_Retry(0x6B, WHO_AM_I_REG, &whoami)) {
        sprintf(msg, "  0x6B: WHO_AM_I = 0x%02X\r\n", whoami);
        UART_Print(msg);
        if(whoami == WHO_AM_I_VALUE) {
            g_sensor_addr = 0x6B;
            UART_Print(">>> Sensor found at 0x6B!\r\n");
            return;
        }
    }

    g_sensor_addr = 0;
    UART_Print(">>> SENSOR NOT FOUND!\r\n");
}

bool Sensor_Init(void) {
    uint8_t reg;
    char msg[64];

    if(g_sensor_addr == 0) return false;

    UART_Print("\r\nInitializing LSM6DSV16X...\r\n");

    /* Software reset */
    UART_Print("  Software reset...\r\n");
    if(!I2C1_Write_Retry(g_sensor_addr, CTRL3_C, 0x01)) {
        UART_Print("  FAILED!\r\n");
        return false;
    }
    DelayMs(100);

    /* Enable BDU (Block Data Update) and IF_INC */
    UART_Print("  Enabling BDU...\r\n");
    if(!I2C1_Write_Retry(g_sensor_addr, CTRL3_C, 0x44)) {
        UART_Print("  FAILED!\r\n");
        return false;
    }
    DelayMs(10);

    /* Configure accelerometer: 104Hz, ±4g */
    UART_Print("  Configuring accelerometer (104Hz, ±4g)...\r\n");
    if(!I2C1_Write_Retry(g_sensor_addr, CTRL1_XL, XL_ODR_104Hz | XL_FS_4g)) {
        UART_Print("  FAILED!\r\n");
        return false;
    }
    DelayMs(10);

    /* Configure gyroscope: 104Hz, ±1000dps */
    UART_Print("  Configuring gyroscope (104Hz, ±1000dps)...\r\n");
    if(!I2C1_Write_Retry(g_sensor_addr, CTRL2_G, G_ODR_104Hz | G_FS_1000dps)) {
        UART_Print("  FAILED!\r\n");
        return false;
    }
    DelayMs(10);

    /* Verify configuration */
    I2C1_Read_Retry(g_sensor_addr, CTRL1_XL, &reg);
    sprintf(msg, "  CTRL1_XL = 0x%02X (Expected: 0x34)\r\n", reg);
    UART_Print(msg);

    I2C1_Read_Retry(g_sensor_addr, CTRL2_G, &reg);
    sprintf(msg, "  CTRL2_G  = 0x%02X (Expected: 0x3C)\r\n", reg);
    UART_Print(msg);

    UART_Print(">>> Sensor initialized!\r\n");
    return true;
}

bool Sensor_Read(sensor_raw_t *data) {
    uint8_t low, high;

    if(g_sensor_addr == 0) return false;

    /* Read temperature */
    if(!I2C1_Read_Retry(g_sensor_addr, OUT_TEMP_L, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUT_TEMP_H, &high)) return false;
    data->temperature = (int16_t)((high << 8) | low);

    /* Read gyroscope */
    if(!I2C1_Read_Retry(g_sensor_addr, OUTX_L_G, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTX_H_G, &high)) return false;
    data->gx = (int16_t)((high << 8) | low);

    if(!I2C1_Read_Retry(g_sensor_addr, OUTY_L_G, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTY_H_G, &high)) return false;
    data->gy = (int16_t)((high << 8) | low);

    if(!I2C1_Read_Retry(g_sensor_addr, OUTZ_L_G, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTZ_H_G, &high)) return false;
    data->gz = (int16_t)((high << 8) | low);

    /* Read accelerometer */
    if(!I2C1_Read_Retry(g_sensor_addr, OUTX_L_A, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTX_H_A, &high)) return false;
    data->ax = (int16_t)((high << 8) | low);

    if(!I2C1_Read_Retry(g_sensor_addr, OUTY_L_A, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTY_H_A, &high)) return false;
    data->ay = (int16_t)((high << 8) | low);

    if(!I2C1_Read_Retry(g_sensor_addr, OUTZ_L_A, &low)) return false;
    if(!I2C1_Read_Retry(g_sensor_addr, OUTZ_H_A, &high)) return false;
    data->az = (int16_t)((high << 8) | low);

    return true;
}

/* =========================================================
   OLED Driver
========================================================= */
void OLED_Command(uint8_t cmd) {
    uint8_t d[2] = {0x00, cmd};
    I2C0_Write(OLED_ADDR, d, 2);
    DelayUs(50);
}

void OLED_Init(void) {
    DelayMs(100);
    OLED_Command(0xAE);
    DelayMs(10);
    OLED_Command(0xD5);
    OLED_Command(0x80);
    DelayUs(10);
    OLED_Command(0xA8);
    OLED_Command(0x3F);
    DelayUs(10);
    OLED_Command(0xD3);
    OLED_Command(0x00);
    DelayUs(10);
    OLED_Command(0x40);
    DelayUs(10);
    OLED_Command(0x8D);
    OLED_Command(0x14);
    DelayMs(50);
    OLED_Command(0x20);
    OLED_Command(0x02);
    DelayUs(10);
    OLED_Command(0xA1);
    DelayUs(10);
    OLED_Command(0xC8);
    DelayUs(10);
    OLED_Command(0xDA);
    OLED_Command(0x12);
    DelayUs(10);
    OLED_Command(0x81);
    OLED_Command(0xCF);
    DelayUs(10);
    OLED_Command(0xD9);
    OLED_Command(0xF1);
    DelayUs(10);
    OLED_Command(0xDB);
    OLED_Command(0x20);
    DelayUs(10);
    OLED_Command(0xA4);
    DelayUs(10);
    OLED_Command(0xA6);
    DelayUs(10);
    OLED_Command(0xAF);
    DelayMs(100);
    OLED_Clear();
    OLED_Update();
}

void OLED_SetCursor(uint8_t col, uint8_t page) {
    OLED_Command(0xB0 | (page & 0x07));
    OLED_Command(0x00 | (col & 0x0F));
    OLED_Command(0x10 | ((col >> 4) & 0x0F));
    DelayUs(10);
}

void OLED_Clear(void) {
    memset(oled_buffer, 0x00, sizeof(oled_buffer));
}

void OLED_Update(void) {
    uint8_t page;
    uint8_t tx_buffer[129];
    tx_buffer[0] = 0x40;

    for(page = 0; page < OLED_PAGES; page++) {
        OLED_SetCursor(0, page);
        memcpy(&tx_buffer[1], &oled_buffer[page * OLED_WIDTH], OLED_WIDTH);
        I2C0_Write(OLED_ADDR, tx_buffer, OLED_WIDTH + 1);
        DelayUs(100);
    }
}

void OLED_DrawPixel(uint8_t x, uint8_t y, bool color) {
    uint16_t index;
    uint8_t bit;
    if(x >= OLED_WIDTH || y >= OLED_HEIGHT) return;
    index = x + ((y >> 3) * OLED_WIDTH);
    bit = y & 0x07;
    if(color)
        oled_buffer[index] |= (1 << bit);
    else
        oled_buffer[index] &= ~(1 << bit);
}

void OLED_DrawChar(uint8_t x, uint8_t y, char ch) {
    const uint8_t *bitmap = Font8x8_GetBitmap(ch);
    uint8_t row, col, sx, sy;
    uint8_t scale = g_font_scale;
    int16_t pixel_x, pixel_y;
    uint8_t line;

    for(row = 0; row < 8; row++) {
        line = bitmap[row];
        for(col = 0; col < 8; col++) {
            if(line & (1 << (7 - col))) {
                for(sx = 0; sx < scale; sx++) {
                    for(sy = 0; sy < scale; sy++) {
                        pixel_x = x + col * scale + sx;
                        pixel_y = y + row * scale + sy;
                        if(pixel_x < OLED_WIDTH && pixel_y < OLED_HEIGHT) {
                            OLED_DrawPixel(pixel_x, pixel_y, true);
                        }
                    }
                }
            }
        }
    }
}

void OLED_DrawString(uint8_t x, uint8_t y, char *str) {
    uint8_t scale = g_font_scale;
    uint8_t orig_x = x;

    while(*str) {
        OLED_DrawChar(x, y, *str++);
        x += 8 * scale;
        if(x + 8 * scale >= OLED_WIDTH) {
            x = orig_x;
            y += 8 * scale;
            if(y + 8 * scale >= OLED_HEIGHT) break;
        }
    }
}

void OLED_TestPattern(void) {
   // uint8_t i;

    OLED_Clear();
    OLED_DrawString(0, 0, "OLED Test");
    OLED_DrawString(0, 16, "LSM6DSV16X");
    OLED_DrawString(0, 32, "TM4C129");
    OLED_DrawString(0, 48, "Ready!");
    OLED_Update();
    DelayMs(2000);
    OLED_Clear();
}

/* =========================================================
   Display Function - Shows live sensor values
========================================================= */
void Display_Data(bool sensor_ok) {
    char buf[32];
    int16_t ax_mg, ay_mg, az_mg;
    int16_t gx_dps, gy_dps, gz_dps;
    int16_t temp_c;
    static uint16_t counter = 0;

    OLED_Clear();

    if(sensor_ok) {
        /* Convert raw values to readable units */
        ax_mg = (int16_t)(((int32_t)sensor_raw.ax * 488) / 1000);
        ay_mg = (int16_t)(((int32_t)sensor_raw.ay * 488) / 1000);
        az_mg = (int16_t)(((int32_t)sensor_raw.az * 488) / 1000);
        gx_dps = (int16_t)(((int32_t)sensor_raw.gx * 175) / 10000);
        gy_dps = (int16_t)(((int32_t)sensor_raw.gy * 175) / 10000);
        gz_dps = (int16_t)(((int32_t)sensor_raw.gz * 175) / 10000);
        temp_c = (int16_t)(25 + (sensor_raw.temperature / 256));

        /* Line 0: Title */
        OLED_DrawString(0, 0, "LSM6DSV16X");

        /* Line 1: Temperature */
        sprintf(buf, "T:%3dC  R:%4d", temp_c, sensor_raw.temperature);
        OLED_DrawString(0, 8, buf);

        /* Line 2: Accelerometer X */
        sprintf(buf, "AX:%+5d mg", ax_mg);
        OLED_DrawString(0, 16, buf);

        /* Line 3: Accelerometer Y */
        sprintf(buf, "AY:%+5d mg", ay_mg);
        OLED_DrawString(0, 24, buf);

        /* Line 4: Accelerometer Z */
        sprintf(buf, "AZ:%+5d mg", az_mg);
        OLED_DrawString(0, 32, buf);

        /* Line 5: Gyroscope X & Y */
        sprintf(buf, "GX:%+3d GY:%+3d", gx_dps, gy_dps);
        OLED_DrawString(0, 40, buf);

        /* Line 6: Gyroscope Z */
        sprintf(buf, "GZ:%+4d", gz_dps);
        OLED_DrawString(0, 48, buf);

        /* Line 7: Counter */
        counter++;
        if(counter > 999) counter = 0;
        sprintf(buf, "OK  #%04d", counter);
        OLED_DrawString(0, 56, buf);

    } else {
        OLED_DrawString(0, 0, "SENSOR ERROR!");
        OLED_DrawString(0, 16, "Check wiring:");
        OLED_DrawString(0, 24, "VCC->3.3V");
        OLED_DrawString(0, 32, "GND->GND");
        OLED_DrawString(0, 40, "SCL->PG0");
        OLED_DrawString(0, 48, "SDA->PG1");
        OLED_DrawString(0, 56, "Pullups:4.7k");
    }

    OLED_Update();
}

/* =========================================================
   MAIN
========================================================= */
int main(void) {
    char msg[128];
    uint32_t loop_count = 0;
    bool sensor_ok = false;
    uint32_t error_count = 0;

    g_ui32SysClock = SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN |
                                        SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, 120000000);

    UART0_Init();
    DelayMs(100);

    UART_Print("\r\n========================================\r\n");
    UART_Print("TM4C129 + LSM6DSV16X + SSD1306 OLED\r\n");
    UART_Print("System Clock: 120MHz\r\n");
    UART_Print("========================================\r\n\r\n");

    I2C0_Init();
    I2C1_Init();

    OLED_Init();
    UART_Print("OLED Initialized\r\n");

    OLED_TestPattern();

    Sensor_Probe();

    if(g_sensor_addr != 0) {
        sensor_ok = Sensor_Init();
        if(sensor_ok) {
            UART_Print("\r\n>>> SENSOR READY! <<<\r\n");
            UART_Print("========================================\r\n\r\n");
        } else {
            UART_Print("Sensor init failed!\r\n");
        }
    } else {
        UART_Print("Sensor not found!\r\n");
    }

    while(1) {
        bool success = false;

        if(sensor_ok) {
            success = Sensor_Read(&sensor_raw);

            if(success) {
                error_count = 0;

                int16_t ax_mg = (int16_t)(((int32_t)sensor_raw.ax * 488) / 1000);
                int16_t ay_mg = (int16_t)(((int32_t)sensor_raw.ay * 488) / 1000);
                int16_t az_mg = (int16_t)(((int32_t)sensor_raw.az * 488) / 1000);
                int16_t gx_dps = (int16_t)(((int32_t)sensor_raw.gx * 175) / 10000);
                int16_t gy_dps = (int16_t)(((int32_t)sensor_raw.gy * 175) / 10000);
                int16_t gz_dps = (int16_t)(((int32_t)sensor_raw.gz * 175) / 10000);
                int16_t temp_c = (int16_t)(25 + (sensor_raw.temperature / 256));

                if(loop_count % 10 == 0) {
                    sprintf(msg, "[%4lu] T=%2dC AX=%+4dmg AY=%+4dmg AZ=%+4dmg | GX=%+3d GY=%+3d GZ=%+3d dps\r\n",
                            loop_count, temp_c, ax_mg, ay_mg, az_mg,
                            gx_dps, gy_dps, gz_dps);
                    UART_Print(msg);
                }
                loop_count++;

            } else {
                error_count++;
                if(error_count == 1) {
                    UART_Print("Read error! Retrying...\r\n");
                }
                if(error_count > 20) {
                    UART_Print("Too many errors, reinitializing...\r\n");
                    sensor_ok = Sensor_Init();
                    error_count = 0;
                }
                DelayMs(50);
                continue;
            }
        }

        Display_Data(success);
        DelayMs(200);
    }
}
