// Driver afficheur ADA 1983/chip ILI9341 pour Kit STM32L476 IUT1 de Grenoble Dpt GEII // // Adaptations L476, et fusion libs : V. GRENNERAT, IUT1 de Grenoble, Dpt GEII // Correction bug coordonnees X et Y pour Draw_Char et Draw_Text : V. GRENNERAT // Ajout fonction LCD_Draw_Image_XY : V. GRENNERAT // Intégration des fonctions de configuration SPI3 et GPIO : V. GRENNERAT // Version 2 (1/12/2019) : // Suppression de l'utilisation de la HAL STM32 : V. GRENNERAT // Diverses optimisations, dans les burst images. // -------------------------------- // MIT License // // Copyright (c) 2017 Matej Artnak // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // /* Includes ------------------------------------------------------------------*/ #include "LCD_driver.h" #include "5x5_font.h" #include "stm32f1xx.h" /* Global Variables ------------------------------------------------------------------*/ volatile uint16_t LCD_HEIGHT = LCD_SCREEN_HEIGHT; volatile uint16_t LCD_WIDTH = LCD_SCREEN_WIDTH; /* SPI3 & GPIOs init function */ void LCD_SPI_Init(void) { //__HAL_RCC_SPI3_CLK_ENABLE(); RCC->APB1ENR1 |= RCC_APB1ENR1_SPI3EN;// SPI3 clock enable RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;// GPIOC clock enable /** CS & DC GPIO signals configuration PC8 ------> LCD_CS_PIN PC11 ------> LCD_DC_PIN */ GPIOC->BSRR = LCD_CS_PIN | (LCD_DC_PIN << 16);//CS à 1 et DC à 0 GPIOC->MODER |= (1 << 16) | (1 << 22);//GPIO out GPIOC->MODER &= ~((1 << 17) | (1 << 23));//mise à 0 GPIOC->OTYPER &= (GPIO_OTYPER_OT8 | GPIO_OTYPER_OT11);//PC8 & 11 en PP GPIOC->OSPEEDR |= (3 << 16) | (3 << 22);//High speed /**SPI3 GPIO Configuration PC10 ------> SPI3_SCK PC12 ------> SPI3_MOSI */ GPIOC->AFR[1] |= (6 << 8) | (6 << 16);//PC10 en AF6 : SPI3_SCK, PC12 en AF6 : SPI3_MOSI GPIOC->AFR[1] &= 0xFFF6F6FF;//Mise à 0 GPIOC->MODER |= (2 << 20) | (2 << 24);//MODE AF GPIOC->MODER &= 0xFEEFFFFF;//Mise à 0 GPIOC->OTYPER &= (GPIO_OTYPER_OT10 | GPIO_OTYPER_OT12);//PC10 & 12 en PP GPIOC->OSPEEDR |= (3 << 24) | (3 << 20);//High speed /*Configure module SPI3*/ SPI3->CR1 = SPI_CR1_SSM | SPI_CR1_SSI;//CS soft, SSI à 1 sinon decl. mode fault SPI3->CR1 |= SPI_CR1_MSTR;// 0 sauf SPE et mode Master, BR = 0 => /2=> Fsck=40M SPI3->CR2 = 0x0700 | SPI_CR2_FRXTH;// | SPI_CR2_NSSP; //mode 8 bits, ITs disabled, no DMA, FRXTH doit être à 1 en 8 bits SPI3->CR1 |= SPI_CR1_SPE; } /* Send command (char) to LCD via SPI bus */ void LCD_Write_Command(uint8_t Command) { CMD ; //CS_ON; while ((SPI1->SR & SPI_SR_TXE) != 0);// Waiting for TX register to be available. *(uint8_t*) (SPI1_DR_ADR) = Command;// Cast sur pointeur, pour ecriture 8 bits. Sinon l'acces 16 bits provoque un tfert 16 bits //while ((SPI1->SR & SPI_SR_BSY) != 0); //Attendre fin envoi trame (cf RM P1289) //CS_OFF; } /* Send Data (char) to LCD via SPI bus */ void LCD_Write_Data(uint8_t Data) { DATA ; //CS_ON; while ((SPI1->SR & SPI_SR_TXE) != 0);// Waiting for TX register to be available. *(uint8_t*) (SPI1_DR_ADR) = Data; //while ((SPI1->SR & SPI_SR_BSY) != 0); //Attendre fin envoi trame (cf RM P1289) //CS_OFF; } /* Set the frame to draw into and sends a write into frame command */ void LCD_Set_Address(uint16_t X1, uint16_t Y1, uint16_t X2, uint16_t Y2) { LCD_Write_Command(0x2A); LCD_Write_Data(X1 >> 8); LCD_Write_Data(X1); LCD_Write_Data(X2 >> 8); LCD_Write_Data(X2); LCD_Write_Command(0x2B); LCD_Write_Data(Y1 >> 8); LCD_Write_Data(Y1); LCD_Write_Data(Y2 >> 8); LCD_Write_Data(Y2); LCD_Write_Command(0x2C); } /*HARDWARE RESET*/ //Reset n'est pas cable sur aff ADA1983 // void LCD_Reset(void) // { // HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET); // HAL_Delay(200); // CS_ON; // HAL_Delay(200); // HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET); // } /*Ser rotation of the screen - changes x0 and y0*/ void LCD_Set_Rotation(uint8_t Rotation) { uint8_t screen_rotation = Rotation; LCD_Write_Command(0x36); for (volatile uint32_t i = 0; i < TEMPO1MS_80M; i++);//~1ms attente switch (screen_rotation) { case SCREEN_VERTICAL_1: LCD_Write_Data(0x40 | 0x08); LCD_WIDTH = 240; LCD_HEIGHT = 320; break; case SCREEN_HORIZONTAL_1: LCD_Write_Data(0x20 | 0x08); LCD_WIDTH = 320; LCD_HEIGHT = 240; break; case SCREEN_VERTICAL_2: LCD_Write_Data(0x80 | 0x08); LCD_WIDTH = 240; LCD_HEIGHT = 320; break; case SCREEN_HORIZONTAL_2: LCD_Write_Data(0x40 | 0x80 | 0x20 | 0x08); LCD_WIDTH = 320; LCD_HEIGHT = 240; break; default: //EXIT IF SCREEN ROTATION NOT VALID! break; } } /*Enable LCD display*/ //Reset n'est pas cable sur aff ADA1983 // void LCD_Enable(void) // { // HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET); // } /*Initialize LCD display*/ void LCD_Init(void) { volatile uint32_t i;//Boucle att //Reset n'est pas cable sur aff ADA1983 //LCD_Enable(); LCD_SPI_Init(); //LCD_Reset(); //SOFTWARE RESET LCD_Write_Command(0x01); for (i = 0; i < (1000 * TEMPO1MS_80M); i++);// ~1s sleep @80M //POWER CONTROL A -- command not existing // LCD_Write_Command(0xCB); // LCD_Write_Data(0x39); // LCD_Write_Data(0x2C); // LCD_Write_Data(0x00); // LCD_Write_Data(0x34); // LCD_Write_Data(0x02); //POWER CONTROL B -- command not existing // LCD_Write_Command(0xCF); // LCD_Write_Data(0x00); // LCD_Write_Data(0xC1); // LCD_Write_Data(0x30); //DRIVER TIMING CONTROL A -- Display Output Ctrl Adjust LCD_Write_Command(0xE8); LCD_Write_Data(0x85); LCD_Write_Data(0x00); LCD_Write_Data(0x78); //DRIVER TIMING CONTROL B -- command not existing // LCD_Write_Command(0xEA); // LCD_Write_Data(0x00); // LCD_Write_Data(0x00); //POWER ON SEQUENCE CONTROL -- command not existing // LCD_Write_Command(0xED); // LCD_Write_Data(0x64); // LCD_Write_Data(0x03); // LCD_Write_Data(0x12); // LCD_Write_Data(0x81); //PUMP RATIO CONTROL -- command not existing // LCD_Write_Command(0xF7); // LCD_Write_Data(0x20); //POWER CONTROL,VRH[5:0] -- Power Control 1 LCD_Write_Command(0xC0); LCD_Write_Data(0x23); //POWER CONTROL,SAP[2:0];BT[3:0] -- Power Control 2 LCD_Write_Command(0xC1); LCD_Write_Data(0x10); //VCM CONTROL -- VCOM Control LCD_Write_Command(0xC5); LCD_Write_Data(0x3E); LCD_Write_Data(0x28); //VCM CONTROL 2 LCD_Write_Command(0xC7); LCD_Write_Data(0x86); //MEMORY ACCESS CONTROL LCD_Write_Command(0x36); LCD_Write_Data(0x48); //PIXEL FORMAT LCD_Write_Command(0x3A); LCD_Write_Data(0x55); //FRAME RATIO CONTROL, STANDARD RGB COLOR LCD_Write_Command(0xB1); LCD_Write_Data(0x00); LCD_Write_Data(0x18); //DISPLAY FUNCTION CONTROL LCD_Write_Command(0xB6); LCD_Write_Data(0x08); LCD_Write_Data(0x82); LCD_Write_Data(0x27); //3GAMMA FUNCTION DISABLE LCD_Write_Command(0xF2); LCD_Write_Data(0x00); //GAMMA CURVE SELECTED LCD_Write_Command(0x26); LCD_Write_Data(0x01); //POSITIVE GAMMA CORRECTION LCD_Write_Command(0xE0); LCD_Write_Data(0x0F); LCD_Write_Data(0x31); LCD_Write_Data(0x2B); LCD_Write_Data(0x0C); LCD_Write_Data(0x0E); LCD_Write_Data(0x08); LCD_Write_Data(0x4E); LCD_Write_Data(0xF1); LCD_Write_Data(0x37); LCD_Write_Data(0x07); LCD_Write_Data(0x10); LCD_Write_Data(0x03); LCD_Write_Data(0x0E); LCD_Write_Data(0x09); LCD_Write_Data(0x00); //NEGATIVE GAMMA CORRECTION LCD_Write_Command(0xE1); LCD_Write_Data(0x00); LCD_Write_Data(0x0E); LCD_Write_Data(0x14); LCD_Write_Data(0x03); LCD_Write_Data(0x11); LCD_Write_Data(0x07); LCD_Write_Data(0x31); LCD_Write_Data(0xC1); LCD_Write_Data(0x48); LCD_Write_Data(0x08); LCD_Write_Data(0x0F); LCD_Write_Data(0x0C); LCD_Write_Data(0x31); LCD_Write_Data(0x36); LCD_Write_Data(0x0F); //EXIT SLEEP LCD_Write_Command(0x11); for (i = 0; i < (120 / TEMPO1MS_80M); i++); // ~120ms sleep //TURN ON DISPLAY LCD_Write_Command(0x29); //STARTING ROTATION LCD_Set_Rotation(SCREEN_HORIZONTAL_1); } //INTERNAL FUNCTION OF LIBRARY /*Sends block colour information to LCD*/ void LCD_Draw_Colour_Burst(uint16_t Colour, uint32_t Size) { short bufColour; // On envoie la même couleur sur Size pixels bufColour = Colour >> 8;// pour le tfert dans DR en 1 seul write mais bufColour |= Colour << 8;// en mode 8 bits, il faut inverser les octets MSB/LSB DATA ; CS_ON; for (uint32_t j = 0; j < Size; j++) { while ((SPI3->SR & SPI_SR_TXE) == 0);//Si FIFO full (TX buffer Empty=0), on attend SPI3->DR = bufColour; } while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; } //FILL THE ENTIRE SCREEN WITH SELECTED COLOUR (either #define-d ones or custom 16bit) /*Sets address (entire screen) and Sends Height*Width ammount of colour information to LCD*/ void LCD_Fill_Screen(uint16_t Colour) { LCD_Set_Address(0, 0, LCD_WIDTH, LCD_HEIGHT); LCD_Draw_Colour_Burst(Colour, LCD_WIDTH * LCD_HEIGHT); } //DRAW PIXEL AT XY POSITION WITH SELECTED COLOUR // //Location is dependant on screen orientation. x0 and y0 locations change with orientations. //Using pixels to draw big simple structures is not recommended as it is really slow //Try using either rectangles or lines if possible // void LCD_Draw_Pixel(uint16_t X, uint16_t Y, uint16_t Colour) { if ((X >= LCD_WIDTH) || (Y >= LCD_HEIGHT)) return;//OUT OF BOUNDS! //ADDRESS LCD_Write_Command(0x2A); //XDATA DATA ; CS_ON; SPI3->DR = (X >> 8) | (X << 8);//inversion MSB / LSB pour envoi des 2 mots 8 bits en 1W 16bits //Pas d'att si FIFO full (TX buffer Empty=0) car juste 2 écriture 16 bits tiennent dans FIFO SPI3->DR = ((X + 1) >> 8) | ((X + 1) << 8); while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; //ADDRESS LCD_Write_Command(0x2B); //YDATA DATA ; CS_ON; SPI3->DR = (Y >> 8) | (Y << 8); SPI3->DR = ((Y + 1) >> 8) | ((Y + 1) << 8); while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; //ADDRESS LCD_Write_Command(0x2C); //COLOUR DATA ; CS_ON; SPI3->DR = (Colour >> 8) | (Colour << 8); while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; } //DRAW RECTANGLE OF SET SIZE AND HEIGTH AT X and Y POSITION WITH CUSTOM COLOUR // //Rectangle is hollow. X and Y positions mark the upper left corner of rectangle //As with all other draw calls x0 and y0 locations dependant on screen orientation // void LCD_Draw_Rectangle( uint16_t X, uint16_t Y, uint16_t Width, uint16_t Height, uint16_t Colour) { if ((X >= LCD_WIDTH) || (Y >= LCD_HEIGHT)) return; if ((X + Width - 1) >= LCD_WIDTH) { Width = LCD_WIDTH - X; } if ((Y + Height - 1) >= LCD_HEIGHT) { Height = LCD_HEIGHT - Y; } LCD_Set_Address(X, Y, X + Width - 1, Y + Height - 1); LCD_Draw_Colour_Burst(Colour, Height * Width); } //DRAW LINE FROM X,Y LOCATION to X+Width,Y LOCATION void LCD_Draw_Horizontal_Line( uint16_t X, uint16_t Y, uint16_t Width, uint16_t Colour) { if ((X >= LCD_WIDTH) || (Y >= LCD_HEIGHT)) return; if ((X + Width - 1) >= LCD_WIDTH) { Width = LCD_WIDTH - X; } LCD_Set_Address(X, Y, X + Width - 1, Y); LCD_Draw_Colour_Burst(Colour, Width); } //DRAW LINE FROM X,Y LOCATION to X,Y+Height LOCATION void LCD_Draw_Vertical_Line( uint16_t X, uint16_t Y, uint16_t Height, uint16_t Colour) { if ((X >= LCD_WIDTH) || (Y >= LCD_HEIGHT)) return; if ((Y + Height - 1) >= LCD_HEIGHT) { Height = LCD_HEIGHT - Y; } LCD_Set_Address(X, Y, X, Y + Height - 1); LCD_Draw_Colour_Burst(Colour, Height); } /*********************Partie de la Lib issue de LCD_GFX**************************/ /*Draw hollow circle at X,Y location with specified radius and colour. X and Y represent circles center */ void LCD_Draw_Hollow_Circle( uint16_t X, uint16_t Y, uint16_t Radius, uint16_t Colour) { int x = Radius - 1; int y = 0; int dx = 1; int dy = 1; int err = dx - (Radius << 1); while (x >= y) { LCD_Draw_Pixel(X + x, Y + y, Colour); LCD_Draw_Pixel(X + y, Y + x, Colour); LCD_Draw_Pixel(X - y, Y + x, Colour); LCD_Draw_Pixel(X - x, Y + y, Colour); LCD_Draw_Pixel(X - x, Y - y, Colour); LCD_Draw_Pixel(X - y, Y - x, Colour); LCD_Draw_Pixel(X + y, Y - x, Colour); LCD_Draw_Pixel(X + x, Y - y, Colour); if (err <= 0) { y++; err += dy; dy += 2; } if (err > 0) { x--; dx += 2; err += (-Radius << 1) + dx; } } } /*Draw filled circle at X,Y location with specified radius and colour. X and Y represent circles center */ void LCD_Draw_Filled_Circle( uint16_t X, uint16_t Y, uint16_t Radius, uint16_t Colour) { int x = Radius; int y = 0; int xChange = 1 - (Radius << 1); int yChange = 0; int radiusError = 0; while (x >= y) { for (int i = X - x; i <= X + x; i++) { LCD_Draw_Pixel(i, Y + y, Colour); LCD_Draw_Pixel(i, Y - y, Colour); } for (int i = X - y; i <= X + y; i++) { LCD_Draw_Pixel(i, Y + x, Colour); LCD_Draw_Pixel(i, Y - x, Colour); } y++; radiusError += yChange; yChange += 2; if (((radiusError << 1) + xChange) > 0) { x--; radiusError += xChange; xChange += 2; } } //Really slow implementation, will require future overhaul //TODO: https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles } /*Draw a hollow rectangle between positions X0,Y0 and X1,Y1 with specified colour*/ void LCD_Draw_Hollow_Rectangle_Coord( uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Colour) { uint16_t X_length = 0; uint16_t Y_length = 0; uint8_t Negative_X = 0; uint8_t Negative_Y = 0; float Calc_Negative = 0; Calc_Negative = X1 - X0; if (Calc_Negative < 0) Negative_X = 1; Calc_Negative = 0; Calc_Negative = Y1 - Y0; if (Calc_Negative < 0) Negative_Y = 1; //DRAW HORIZONTAL! if (!Negative_X) { X_length = X1 - X0; } else { X_length = X0 - X1; } LCD_Draw_Horizontal_Line(X0, Y0, X_length, Colour); LCD_Draw_Horizontal_Line(X0, Y1, X_length, Colour); //DRAW VERTICAL! if (!Negative_Y) { Y_length = Y1 - Y0; } else { Y_length = Y0 - Y1; } LCD_Draw_Vertical_Line(X0, Y0, Y_length, Colour); LCD_Draw_Vertical_Line(X1, Y0, Y_length, Colour); if ((X_length > 0) || (Y_length > 0)) { LCD_Draw_Pixel(X1, Y1, Colour); } } /*Draw a filled rectangle between positions X0,Y0 and X1,Y1 with specified colour*/ void LCD_Draw_Filled_Rectangle_Coord( uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Colour) { uint16_t X_length = 0; uint16_t Y_length = 0; uint8_t Negative_X = 0; uint8_t Negative_Y = 0; int32_t Calc_Negative = 0; uint16_t X0_true = 0; uint16_t Y0_true = 0; Calc_Negative = X1 - X0; if (Calc_Negative < 0) Negative_X = 1; Calc_Negative = 0; Calc_Negative = Y1 - Y0; if (Calc_Negative < 0) Negative_Y = 1; //DRAW HORIZONTAL! if (!Negative_X) { X_length = X1 - X0; X0_true = X0; } else { X_length = X0 - X1; X0_true = X1; } //DRAW VERTICAL! if (!Negative_Y) { Y_length = Y1 - Y0; Y0_true = Y0; } else { Y_length = Y0 - Y1; Y0_true = Y1; } LCD_Draw_Rectangle(X0_true, Y0_true, X_length, Y_length, Colour); } /*Draws a character (fonts imported from fonts.h) at X,Y location with specified font colour, size and Background colour*/ /*See fonts.h implementation of font on what is required for changing to a different font when switching fonts libraries*/ void LCD_Draw_Char( char Character, uint16_t X, uint16_t Y, uint16_t Colour, uint16_t Size, uint16_t Background_Colour) { uint8_t function_char; uint8_t i, j; function_char = Character; if (function_char < ' ') { Character = 0; } else { function_char -= 32; } char temp[CHAR_WIDTH]; for (uint8_t k = 0; k < CHAR_WIDTH; k++) { temp[k] = font[function_char][k]; } // Draw pixels LCD_Draw_Rectangle(X, Y, CHAR_WIDTH * Size, CHAR_HEIGHT * Size, Background_Colour); for (j = 0; j < CHAR_WIDTH; j++) { for (i = 0; i < CHAR_HEIGHT; i++) { if (temp[j] & (1 << i)) { if (Size == 1) { LCD_Draw_Pixel(X + j, Y + i, Colour); } else { LCD_Draw_Rectangle(X + (j * Size), Y + (i * Size), Size, Size, Colour); } } } } } /*Draws an array of characters (fonts imported from fonts.h) at X,Y location with specified font colour, size and Background colour*/ /*See fonts.h implementation of font on what is required for changing to a different font when switching fonts libraries*/ void LCD_Draw_Text( const char *Text, uint16_t X, uint16_t Y, uint16_t Colour, uint16_t Size, uint16_t Background_Colour) { while (*Text) { LCD_Draw_Char(*Text++, X, Y, Colour, Size, Background_Colour); X += CHAR_WIDTH * Size; } } /*Dessine une image dans une zone de l'ecran, aux coordonnées X et Y*/ //CONVERTISSEUR: http://www.digole.com/tools/PicturetoC_Hex_converter.php //65K colour (2Bytes / Pixel) void LCD_Draw_Image_XY( const char *Image_Array, uint16_t X, uint16_t Y, uint16_t Width, uint16_t Height) { LCD_Set_Address(X, Y, X + Width - 1, Y + Height - 1); DATA ; CS_ON; for (uint32_t i = 0; i < Width * Height * 2; i += 2) { while ((SPI3->SR & SPI_SR_TXE) == 0);//Si FIFO full (TX buffer Empty=0), on attend // on utilise l'ecriture 16 bits dans DR, pour des envois 8 bits // Le LSB doit etre place ds le MSB : SPI3->DR = ((short) Image_Array[i + 1]) << 8 | Image_Array[i]; } while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; } /*Draws a full screen picture from flash. Image converted from RGB .jpeg/other to C array using online converter*/ //USING CONVERTER: http://www.digole.com/tools/PicturetoC_Hex_converter.php //65K colour (2Bytes / Pixel) void LCD_Draw_Image_Full(const char *Image_Array, uint8_t Orientation) { switch (Orientation) { case SCREEN_HORIZONTAL_1: LCD_Set_Rotation(SCREEN_HORIZONTAL_1); LCD_Set_Address(0, 0, LCD_SCREEN_WIDTH, LCD_SCREEN_HEIGHT); break; case SCREEN_HORIZONTAL_2: LCD_Set_Rotation(SCREEN_HORIZONTAL_2); LCD_Set_Address(0, 0, LCD_SCREEN_WIDTH, LCD_SCREEN_HEIGHT); break; case SCREEN_VERTICAL_1: LCD_Set_Rotation(SCREEN_VERTICAL_1); LCD_Set_Address(0, 0, LCD_SCREEN_HEIGHT, LCD_SCREEN_WIDTH); break; case SCREEN_VERTICAL_2: LCD_Set_Rotation(SCREEN_VERTICAL_2); LCD_Set_Address(0, 0, LCD_SCREEN_HEIGHT, LCD_SCREEN_WIDTH); break; } DATA ; CS_ON; for (uint32_t i = 0; i < LCD_SCREEN_WIDTH * LCD_SCREEN_HEIGHT * 2; i += 2) { while ((SPI3->SR & SPI_SR_TXE) == 0);//Si FIFO full (TX buffer Empty=0), on attend // on utilise l'ecriture 16 bits dans DR, pour des envois 8 bits // Le LSB doit etre place ds le MSB : SPI3->DR = ((short) Image_Array[i + 1]) << 8 | Image_Array[i]; } while ((SPI3->SR & SPI_SR_BSY) != 0);//Attendre fin envoi trame CS_OFF; }