/* Correction du TP 11 (partie 2, FreeRTOS) -- tache ecran SPI.
 *
 * Reprend le projet du TP 10 (g_sensors, sensorsMutex, taches capteurs) et
 * y ajoute : SPI3 Transmit Only Master PC10/PC12, Prescaler 16 (4 MHz, mux
 * SPI123 = PER_CK) ; SCREEN_CS PD14 (High au depart), SCREEN_DC PD15,
 * SCREEN_RST PC8 ; DMA SPI3_TX (Normal, octets), SPI3 global interrupt,
 * priorite >= 5 ; tache displayTask (StartDisplay) osPriorityBelowNormal,
 * pile 512 ; semaphore binaire spiDoneSem, initialement indisponible.
 * Script d'edition de liens : section .dma_buffer en RAM_D2 (TP 6), horloges
 * D2SRAM1/2 activees.
 * Ajouter #include <math.h> (USER CODE Includes) ; -lm est deja lie.
 */

/* ===== USER CODE BEGIN PD =================================================== */
#define DOUBLE_BUFFER   1      /* etapes 1-2 : 0 ; etape 3 : 1 */
#define FRAME_MS        40u    /* 200 aux etapes 1-2 ; 40 (25 images/s) ensuite */
#define CX              96     /* centre de la boussole */
#define CY              32
#define RADIUS          20
/* USER CODE END PD */

/* ===== USER CODE BEGIN PV =================================================== */
static uint8_t g_fb[2][1024] __attribute__((section(".dma_buffer"), aligned(32)));
static uint8_t *fb = g_fb[0];      /* tampon ou l'on dessine */

static const uint8_t init_cmds[] = {
    0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0xA1, 0xC8,
    0xDA, 0x12, 0x81, 0x7F, 0xD9, 0x22, 0xDB, 0x34, 0xA4, 0xA6,
    0x20, 0x00, 0xAF,
};
static const uint8_t fenetre[] = { 0x21, 0x00, 0x7F, 0x22, 0x00, 0x07 };
static const uint8_t chiffres[10][5] = {
    { 0x3E, 0x51, 0x49, 0x45, 0x3E }, { 0x00, 0x42, 0x7F, 0x40, 0x00 },
    { 0x42, 0x61, 0x51, 0x49, 0x46 }, { 0x21, 0x41, 0x45, 0x4B, 0x31 },
    { 0x18, 0x14, 0x12, 0x7F, 0x10 }, { 0x27, 0x45, 0x45, 0x45, 0x39 },
    { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, { 0x01, 0x71, 0x09, 0x05, 0x03 },
    { 0x36, 0x49, 0x49, 0x49, 0x36 }, { 0x06, 0x49, 0x49, 0x29, 0x1E },
};
static volatile uint8_t g_spi_busy;  /* un transfert DMA est en cours */
/* USER CODE END PV */

/* ===== USER CODE BEGIN 0 ==================================================== */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi == &hspi3) { osSemaphoreRelease(spiDoneSemHandle); }
}

static void send_commands(const uint8_t *cmds, uint16_t n)
{
    HAL_GPIO_WritePin(SCREEN_DC_GPIO_Port, SCREEN_DC_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(SCREEN_CS_GPIO_Port, SCREEN_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&hspi3, (uint8_t *)cmds, n, 100u);
    HAL_GPIO_WritePin(SCREEN_CS_GPIO_Port, SCREEN_CS_Pin, GPIO_PIN_SET);
}

static void screen_init(void)
{
    HAL_GPIO_WritePin(SCREEN_RST_GPIO_Port, SCREEN_RST_Pin, GPIO_PIN_SET);
    osDelay(10);
    HAL_GPIO_WritePin(SCREEN_RST_GPIO_Port, SCREEN_RST_Pin, GPIO_PIN_RESET);
    osDelay(10);
    HAL_GPIO_WritePin(SCREEN_RST_GPIO_Port, SCREEN_RST_Pin, GPIO_PIN_SET);
    osDelay(10);
    send_commands(init_cmds, (uint16_t)sizeof(init_cmds));
}

/* Attend la fin du transfert en cours, s'il y en a un, et remonte CS. */
static void wait_previous(void)
{
    if (g_spi_busy)
    {
        osSemaphoreAcquire(spiDoneSemHandle, osWaitForever);
        HAL_GPIO_WritePin(SCREEN_CS_GPIO_Port, SCREEN_CS_Pin, GPIO_PIN_SET);
        g_spi_busy = 0u;
    }
}

/* Etape 2 : envoi par DMA. Sans double tampon, on attend la fin ici ; avec,
 * on rend la main aussitot et on change de tampon de dessin. */
static void flush(void)
{
    wait_previous();
    send_commands(fenetre, (uint16_t)sizeof(fenetre));
    SCB_CleanDCache_by_Addr((uint32_t *)fb, 1024);   /* sans effet si D-cache inactif */
    HAL_GPIO_WritePin(SCREEN_DC_GPIO_Port, SCREEN_DC_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(SCREEN_CS_GPIO_Port, SCREEN_CS_Pin, GPIO_PIN_RESET);
    g_spi_busy = 1u;
    HAL_SPI_Transmit_DMA(&hspi3, fb, 1024u);
#if DOUBLE_BUFFER
    fb = (fb == g_fb[0]) ? g_fb[1] : g_fb[0];
#else
    wait_previous();
#endif
}

static void fill(uint8_t v)
{
    for (uint32_t k = 0u; k < 1024u; k++) { fb[k] = v; }
}

static void pixel(int32_t x, int32_t y)
{
    if ((x < 0) || (x >= 128) || (y < 0) || (y >= 64)) { return; }
    fb[x + (y / 8) * 128] |= (uint8_t)(1u << (y % 8));
}

static void digit(uint32_t x, uint32_t page, uint32_t d)
{
    for (uint32_t c = 0u; c < 5u; c++)
    {
        if ((x + c) < 128u) { fb[x + c + page * 128u] = chiffres[d % 10u][c]; }
    }
}

static void number(uint32_t x, uint32_t page, uint32_t v)
{
    char     s[6];
    uint32_t n = 0u;

    do { s[n++] = (char)(v % 10u); v /= 10u; } while ((v != 0u) && (n < 5u));
    while (n > 0u) { digit(x, page, (uint32_t)s[--n]); x += 6u; }
}

/* Etape 3 : Bresenham, tous octants. */
static void line(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{
    int32_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
    int32_t dy = (y1 > y0) ? (y0 - y1) : (y1 - y0);   /* negatif */
    int32_t sx = (x0 < x1) ? 1 : -1;
    int32_t sy = (y0 < y1) ? 1 : -1;
    int32_t err = dx + dy;

    for (;;)
    {
        pixel(x0, y0);
        if ((x0 == x1) && (y0 == y1)) { break; }
        int32_t e2 = 2 * err;

        if (e2 >= dy) { err += dy; x0 += sx; }
        if (e2 <= dx) { err += dx; y0 += sy; }
    }
}

static void circle(int32_t cx, int32_t cy, int32_t r)
{
    for (int32_t a = 0; a < 64; a++)
    {
        float t = (float)a * 6.2831853f / 64.0f;

        pixel(cx + (int32_t)lroundf((float)r * cosf(t)),
              cy + (int32_t)lroundf((float)r * sinf(t)));
    }
}
/* USER CODE END 0 */

void StartDisplay(void *argument)
{
    uint32_t next;

    (void)argument;
    screen_init();
    next = osKernelGetTickCount();
    for (;;)
    {
        sensors_t s;
        int32_t   t_dC;
        float     a;

        osMutexAcquire(sensorsMutexHandle, osWaitForever);
        s = g_sensors;
        osMutexRelease(sensorsMutexHandle);

        fill(0x00u);
        /* temperature en dixiemes de degre, page 1 ; cap en degres, page 5 */
        t_dC = s.temp_mC / 100;
        number(4u, 1u, (uint32_t)((t_dC < 0) ? 0 : t_dC));
        number(4u, 5u, (uint32_t)(((int32_t)lroundf(s.heading_deg) % 360 + 360) % 360));

        /* boussole : 0 degre vers le haut, le cap croit vers la gauche */
        a = s.heading_deg * 3.1415927f / 180.0f;
        circle(CX, CY, RADIUS);
        line(CX, CY,
             CX - (int32_t)lroundf((float)(RADIUS - 2) * sinf(a)),
             CY - (int32_t)lroundf((float)(RADIUS - 2) * cosf(a)));

        flush();
        next += FRAME_MS;
        osDelayUntil(next);
    }
}

/* Reponses attendues aux questions :
 *  1. L'affichage n'est pas urgent : une image en retard ne se voit pas. Au
 *     dessus d'imuTask, ses 2 ms d'envoi bloquant et son dessin retarderaient
 *     la lecture du gyroscope, qui doit rester reguliere (TP 10).
 *  2. Environ 2,1 ms (1024 x 8 bits / 4 MHz, plus les commandes) ; a 5
 *     images/s, ~1 % du temps processeur -- 5 % a 25 images/s.
 *  3. Quelques microsecondes : la preparation du flux DMA. La tache se
 *     bloque ensuite sur le semaphore.
 *  4. CS remonte alors que le DMA envoie encore : l'ecran ignore la suite de
 *     l'image (ou la prend pour une nouvelle commande). Image tronquee ou
 *     corrompue.
 *  5. Nettoyer le cache sur le tampon (SCB_CleanDCache_by_Addr), aligne sur
 *     32 octets, ou placer le tampon dans une zone non cacheable (MPU).
 *  6. Le DMA lirait un tampon en cours de modification : l'ecran
 *     montrerait par moments une image melangee (haut neuf, bas ancien, ou
 *     un ecran vide si fill() passe pendant l'envoi).
 *  7. 4 MHz / (1024 x 8 bits) = 488 images/s, theoriquement. Pour aller
 *     plus vite : prescaler 8 (8 MHz, dans les limites du SSD1309), ou
 *     n'envoyer que les pages qui ont change.
 */
