⬆️ ⬇️

How to stop being afraid and fall in love with mbed [Part 5]

We continue a series of publications devoted to the use of the ARM mbed environment for creating a prototype measuring device.



Today I finally finish the description of the program part - there are questions related to the output on the TFT-display of images and Cyrillic. Let's make everything beautiful.





')

Content of the publication cycle:





Let me remind you that we are talking about the development of a prototype device with a touch screen, which serves for high-speed measurement of relative humidity and (at the same time) temperature. For writing a program for the microcontroller, an online IDE mbed is used, which allows you to create iron-independent code that works equally well on debugging boards from SiLabs, Atmel, Wiznet, STM32, NXP and other manufacturers.



1. Display images on a TFT




The general scheme for controlling a TFT display using an FTDI graphic controller has already been described in previous articles . Like other drawing-related procedures, image output to a TFT display is hardware implemented on the FT801 graphics controller. The controlling host controller is only required to transmit simple control commands and necessary data to the FT801.



The FT8xx series graphics controllers allow you to work with .jpeg or .png images. The image can not only be displayed on the screen, but also transformed - resized, rotated or moved around the screen, and also used as a screen saver. All these operations are performed on the graphics controller by the arrival of the corresponding commands from the managing director MK.



Speaking about loading images onto a graphics controller, you should immediately separate the two approaches to working with images.







In the first case, the .jpeg file is taken from the memory of the managing director of the MC or from external media and is immediately recorded in the memory of the FT8xx graphics controller. In this case, the CMD_LOADIMAGE command is used for loading. After the image is loaded into the FT8xx memory, all functions for working with the image become available - image transformation and its display. This approach is optimal if you have a place to store images, that is, using USB flash memory or an SD card.



In the second case, the image is pre-compressed using the Deflate algorithm. The bitmap obtained as a result of coding takes up much less space, so it can be stored not only on external media, but in the internal memory of the control MC. The image is loaded onto the FT8xx graphics controller in a compressed form, and unpacking of data is already performed by the graphics controller. To download a compressed image, use the CMD_INFLATE command. After the image is unpacked, you can work with it in the same way as in the first case.



Since my project does not assume the use of external memory, we will consider only the first case - working with compressed images stored in the memory of the control microcontroller. As a result, I want to display three images: icons for temperature and relative humidity on the screen with the main menu and a photo of the HYT-271 sensor on the screen with a description of the sensor.











1.1. Image formatting, compression




The Deflate algorithm was originally created for zip archives, but due to the absence of patents, it is successfully used for other purposes, including image compression. Here, by the way, is an excellent article about Deflate and png.



For image conversion, FTDI offers several console utilities , such as img_cvt. Also available are graphical skins like EVE Screen Editor , which not only allow you to convert images, but also make life much easier when creating programs for a graphics controller. In fact, EVE Screen Editor is a Riverdi TFT emulator.







There is absolutely nothing unusual in the program interface - the future TFT screen is formed in the central window - a field to which you can drag the necessary images and other graphic elements.



The FT801 controller used by me allows displaying images of nine formats on the TFT display: black and white L1, L4 and L8, as well as RGB332, ARGB2, ARGB4, RGB565, PALETTED and ARGB1555, you can read more about them in the manufacturer 's documentation . Different formats allow you to get a different ratio of image quality and size of the image file encoding a binary file.





(in the figure from left to right L1, L4, L8, RGB332, ARGB2, ARGB4, RGB565, PALETTED, ARGB1555)



In theory, the best quality can be obtained using the ARGB1555 format, but in practice it is better to try different options and choose the most suitable one. The photo of the sensor really looks best after conversion to ARGB1555 or RGB565, but at some point I did not have enough internal memory MK and I had to abandon these formats in favor of RGB332. It looks more or less decent, and takes 1865 bytes instead of 7067 and 7816 from ARGB1555 and RGB565, respectively.



The icons turned out more interesting. Source files are .png with a transparent background of 37x77 and 53x67 pixels. The transparency of the icons can be saved only with ARGB encoding, i.e. we have to choose from the formats ARGB2, ARGB4, and ARGB1555. Of these, the most attractive smoothed image does not provide ARGB2, and, to my surprise, not ARGB1555, but ARGB4.





(in the picture from left to right ARGB2, ARGB4, ARGB1555)



After selecting the appropriate format, you need to save the project in the Screen Editor. In the folder where the Screen Editor has been installed, the images directory will appear, where for each of the used images a .binh file is created. He then we need.



1.2. Image upload to graphic controller memory




Having received a binary representation of the image, we return from the Screen Editor to the mbed IDE, where in the program code we save all the received bitmap as arrays. An icon for humidity, for example, looks like this.



const unsigned char hum_icon[]={ 120,156,165,86,203,109,195,48,12,205,177,40,250,25,193,35,120,4,141,160,17,60,130,71,200,181,55,143,224,123,46,26,193,11,20,208,4,133,54,168,71,96,31,165,248,43,74,145,82,18,8,20,138,79,228,35,41,193,244,73,117,218,86,250,175,250,251,65,243,115,200,203,133,88,236,83,232,153,130,92,171,145,29,109,82,135,108,214,168,44,117,172,29,209,1,59,22,35,21,197,82,91,165,125,100,45,121,254,188,147,33,131,94,110,255,37,17,177,52,248,189,149,209,247,155,136,85,2,82,195,238,124,109,186,213,102,132,156,83,189,80,152,91,222,111,130,213,207,226,94,92,60,93,152,186,137,150,185,185,98,53,193,178,229,51,223,53,194,121,237,217,255,235,133,215,248,229,115,250,195,126,11,109,68,228,33,79,159,63,75,251,184,135,224,228,162,202,247,167,188,83,58,238,251,178,106,156,119,162,51,219,60,36,121,164,59,35,237,113,189,77,6,107,40,121,163,49,85,46,121,110,200,215,194,39,199,199,74,59,152,116,247,176,19,83,34,242,85,172,111,28,57,226,140,76,185,74,185,58,6,117,130,151,46,136,186,100,215,9,54,93,128,85,240,27,78,182,49,83,255,189,54,2,227,255,96,37,30,146,106,33,103,85,88,171,49,142,113,123,45,234,145,191,17,194,228,249,59,138,233,74,34,113,187,172,204,236,254,182,76,194,253,91,170,100,211,188,128,154,252,219,167,197,26,49,39,147,190,41,216,9,239,185,5,131,150,253,192,65,161,7,206,91,135,240,250,101,84,249,232,103,49,197,95,24,13,226,26,68,183,56,103,196,58,91,255,63,65,221,118,198, }; 




To load this image into the memory of the graphic controller FT801, you will need to send three commands from the managing director of the MC:



 #define IMAGE_ADDR_HUMIDITY 29696 ... (*_TFT).WrCmd32(CMD_INFLATE); (*_TFT).WrCmd32(IMAGE_ADDR_HUMIDITY); (*_TFT).WrCmdBufFromFlash(hum_icon, sizeof(hum_icon)); 


CMD_INFLATE is a command informing FT801 that a compressed image will be written to its memory.

IMAGE_ADDR_HUMIDITY - the starting address in the memory of the graphics controller RAM_G, which will be available image.

hum_icon is an array storing an image.



After such an operation, the “Humidity” icon will be available for display on the screen until the graphics controller memory is cleared programmatically or as a result of a reset.



1.3. Image capture




The next step after loading is to “capture” the image.



 StartDL(); ... (*_TFT).DL(BITMAP_HANDLE(0)); (*_TFT).DL(BITMAP_SOURCE(IMAGE_ADDR_HUMIDITY)); (*_TFT).DL(BITMAP_LAYOUT(ARGB4, 60, 38)); (*_TFT).DL(BITMAP_SIZE(NEAREST, BORDER, BORDER, 30, 38)); ... FinishDL(); 


Using the BITMAP_HANDLE command, we assign to each image (a Bitmap object) a pointer — a number from 0 to 31, which can be used to access the image in the future.

The BITMAP_SOURCE command points to the address in the RAM_G memory of the FT8xx graphics controller (see p. Image Loading).

The BITMAP_LAYOUT command tells the graphics controller the format of the image and its dimensions, and the BITMAP_SIZE command determines the size of the displayed image. By changing its arguments, you can, for example, crop a picture to the right or left.



Capturing an image, as well as loading it into the memory of the graphics controller, is enough to run once after initializing the FT8xx. However, it is important to understand that image capture commands, unlike loading commands, are the so-called display list commands, that is, they can be used only after the start of the display list command and before the end of the display list command.

* The fact that such a display list and what it eats, you can read in the second article of this cycle .



1.4. Image output




After the image has been loaded and captured, it can be displayed on a TFT display. To do this, use the BITMAP group commands, for example, to display the “Humidity” icon on top of one of the main menu buttons, I execute three commands:



 StartDL(); ... //   () ... (*_TFT).DL(BEGIN(BITMAPS)); (*_TFT).DL(VERTEX2II(12 + 255, 62 + 10, 0, 0)); (*_TFT).DL(END()); ... FinishDL(); 


The first two arguments of the VERTEX2II command specify the coordinates for display on the screen, and the third argument is a pointer to the “Humidity” icon, which was specified in BITMAP_SOURCE and BITMAP_HANDLE - “0”.



The output of the Temperature icon and sensor photos are performed in exactly the same
 StartDL(); ... (*_TFT).DL(BEGIN(BITMAPS)); (*_TFT).DL(VERTEX2II(12 + 260, 62 + 93 + 12 + 10, 1, 0)); (*_TFT).DL(END()); ... FinishDL(); 


 StartDL(); ... (*_TFT).DL(BEGIN(BITMAPS)); (*_TFT).DL(VERTEX2II(360, 140, 2, 0)); (*_TFT).DL(END()); ... FinishDL(); 




A link to the complete project source code is provided below.



Using custom fonts




The article on getting started with the FT8xx series graphic controller mentioned the support for standard widgets - procedures for outputting relatively complex graphic objects that are hardware implemented on FTDI graphics controllers. Among the widgets there is a text line, in the mbed-library FT800_2, the Text () function corresponds to the display of a line.



  (*_TFT).Text(22, 67, 27, 0, "Current humidity (rH)"); 


The arguments to the function are the coordinates of the first character of the line (22, 67), the number of the font used (27), additional options (0) and, in fact, a text line. With coordinates everything is clear, additional options also apply only to the position of the line on the screen, so let's talk about fonts.



The number of the font used is a number from 0 to 31, with the numbers from 0 to 15 reserved for user fonts, and the numbers from 16 to 31 correspond to the sixteen built-in fonts. If in your application it is enough to display only the first 128 ASCII characters and the standard outline of these characters is enough for you, then you can stop at this place and not read the article further - just use fonts with numbers 16-31.







If you need non-standard outlines of numbers and Latin characters or require the output of characters that go beyond the standard ASCII set (for example, Cyrillic), then you will have to deal with loading your own fonts.



For FT8xx graphics controllers, user fonts are almost the same bitmap as images, so creating a new font in many ways repeats the process of displaying images.



2.1. Font formatting, compression




At the first stage, you need to download or create the necessary font (the .ttf format is fine) and get a binary file for downloading to the FT8xxx graphics controller. To do this, you can use either a console utility or the same EVE Screen Editor.







In the Screen Editor, the font is imported in the same way as image files — a font file is added to the Content window, and its compression parameters are set in the Properties window: format, size, and charset.



The format is selected from three options - L1, L4 and L8. The difference, as in the conversion of images, in the ratio of the quality of drawing and the size of the binary file. The font size simply determines the width of characters in pixels, and the charset field deserves the most attention.



For FTDI graphics controllers, the default fonts are 128 ASCII characters.



  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ //    ,   32-  


If you use only these characters and add a new font just to change their style - great, convert the font without changing the charset. And if you need to add Cyrillic or some other non-ASCII character, you will have to replace the charset. In my application, you will need all Cyrillic, numbers, some punctuation marks and mathematical symbols, degrees and percentages, as well as several Latin letters. As a result, the modified charset looks like this:



  0123456789.,:-°±%<>rHYTICS //    ,   32-  


Now, just like when working with an image, save the Screen Editor project, go to the folder where EVE Screen Editor is installed and find the binary .binh file in the / fonts directory.



2.2. Download to the graphics controller memory




Downloading a new font repeats the process of loading an image - we save the binary representation of the font as an array and load it on FT8xx at the predefined starting address in the memory of the graphics controller using the CMD_INFLATE command.



 #define FONT_ADDR_ROBOTO_REGULAR_30 16992 ... (*_TFT).WrCmd32(CMD_INFLATE); (*_TFT).WrCmd32(FONT_SET_ROBOTO_REGULAR_30); (*_TFT).WrCmdBufFromFlash(font_RobotoRegular30, sizeof(font_RobotoRegular30)); 




2.3. Capture and install font




The “capture” of the user font is performed by the same commands as the image capture, the difference is that after the BITMAP_HANDLE, BITMAP_SOURCE, BITMAP_LAYOUT and BITMAP_SIZE commands, you need to set a new font via the SetFont () call.



  (*_TFT).DL(BITMAP_HANDLE(3)); (*_TFT).DL(BITMAP_SOURCE(FONT_ADDR_ROBOTO_REGULAR_30)); (*_TFT).DL(BITMAP_LAYOUT(L4, 16, 33)); (*_TFT).DL(BITMAP_SIZE(NEAREST, BORDER, BORDER, 32, 33)); (*_TFT).SetFont(3, FONT_SET_ROBOTO_REGULAR_30); 


2.4. Use font




Now, among the custom fonts, at number 3 is the Roboto Regular font that we downloaded. If during the conversion the charset of this font was not changed, then to change the embedded font number 27 to Roboto Regular, you would only need to change



  (*_TFT).Text(22, 67, 27, 0, "Current humidity (rH)"); 


on



  (*_TFT).Text(22, 67, 3, 0, "Current humidity (rH)"); //     


However, we are going to display characters that are not standard for the FT8xx controller, so instead of explicitly specifying the line (“Current humidity (rH)”), we will have to sculpt this line from individual characters each time.



Consider the line "Relative Humidity". The task is to assign to each character of this line its number in the charset.



If the compiler supports Cyrillic, then each character starting with uppercase letters ABC and ending with lowercase letters (with omission of letters and ) will correspond to a value from 0xC0 to 0xFF. So in order to match the characters of the string with the numbers of these characters in the charset, you need to subtract a fixed value from the code of each character. For example, if the letter A in the charlist occupies the 32nd position (0x20), and the letters following A are in the same order as in table CP1251, then the value of each character of the string “Relative humidity” (except the space) will be subtracted 0xA0.



Cyrillic encoding CP1251
image



However, the compiler may not support Cyrillic, mbed-ovsky just does not support. This means that the compiler does not perceive the Cyrillic alphabet as codes from 0xC0 to 0xFF, so I have no choice but to use Unicode, more precisely, UTF-8.



Each character not included in the main ASCII table — Cyrillic, ° and ± signs — is represented as a two-byte UTF-8 code. I take the code of each character and assign it to the corresponding number in my charset.



For Latin letters that are also in the charlist, you also need to replace Unicode with a number in the charset, the only difference is that the code of Latin letters and other ASCII characters such as a period, comma and percentage consists of one, not two bytes.

Convertible string characterUTF-8 codeOrder number in my charset
ABC ... np0xD090 ... 0xD0BF43 ... 90
rst ... eyyu0xD180 ... 0xD18F91 ... 96
°0xC2B0112
±0xC2B1113
space0x2032
0 ... 90x30 ... 0x3933 ... 43
.0x2E108
,0x2C109
:0x3A110
and so on


To perform such a string conversion, a corresponding function has been created.



CreateStringRussian string conversion function
 void Display::CreateStringRussian(const string rustext) { // CHANGED ASCII: // 0123456789.,:-°±%<>rHYTICS int len = rustext.length(); int j = 0; for (int i = 0; i < len; i ++) { uint16_t res = uint8_t(rustext[i]); if (res > 0x7F) { res = res << 8 | uint8_t(rustext[i + 1]); //  ...  if ((res >= 0xD090) && (res <= 0xD0BF)) { char offset = (char)(res - 0xD090); russianStr[j] = 32 + 11 + offset; //  ...  } else if ((res >= 0xD180) && (res <= 0xD18F)) { char offset = (char)(res - 0xD180); russianStr[j] = 32 + 59 + offset; } // Degree sign else if (res == 0xC2B0) { russianStr[j] = 32 + 79; } // Plus-minus sign else if (res == 0xC2B1) { russianStr[j] = 32 + 80; } i++; } else { // Space if (res == 0x20) { russianStr[j] = 32; } // Numbers else if (res >= 0x30 && res <= 0x39) { russianStr[j] = 32 + 1 + (res - 0x30); } // . else if (res == 0x2E) { russianStr[j] = 32 + 75; } // , else if (res == 0x2C) { russianStr[j] = 32 + 76; } // : else if (res == 0x3A) { russianStr[j] = 32 + 77; } // - else if (res == 0x2D) { russianStr[j] = 32 + 78; } // % else if (res == 0x25) { russianStr[j] = 32 + 81; } // < else if (res == 0x3C) { russianStr[j] = 32 + 82; } // > else if (res == 0x3C) { russianStr[j] = 32 + 83; } // "r" else if (res == 0x72) { russianStr[j] = 32 + 84; } // "H" else if (res == 0x48) { russianStr[j] = 32 + 85; } // "Y" else if (res == 0x59) { russianStr[j] = 32 + 86; } // "T" else if (res == 0x54) { russianStr[j] = 32 + 87; } // "I" else if (res == 0x49) { russianStr[j] = 32 + 88; } // "C" else if (res == 0x43) { russianStr[j] = 32 + 89; } // "S" else if (res == 0x53) { russianStr[j] = 32 + 90; } } j++; } russianStr[j] = 0; } 




Thus, to display the string “Relative humidity” (or any other string in Russian) on the TFT display, you must first perform its conversion, and then use the standard output of the string, not forgetting to specify the font number as the third argument.



  CreateStringRussian(" "); (*_TFT).Text(15, 15, 3, 0, russianStr); 


3. The final result




The source code for the finished project is available on developer.mbed.org . The topic of today's article includes the following project files:





Thus, I finally finish the description of creating a project in the online IDE ARM mbed. We looked at everything from writing mbedov Hello Word to a rather voluminous program using two libraries of peripheral devices - HYT for the sensor of the same name and FT800_2 for the Riverdi TFT-module.



The magic is that the resulting program can be compiled into a working firmware for any of the debug boards supported in mbed .







In the last article of this series I will share the story of the creation of the case for this device.



Conclusion




In conclusion, I traditionally thank the reader for his attention and remind you that questions about the use of products, about which we write on Habré, you can safely ask by email, specified in my profile.

Source: https://habr.com/ru/post/311816/



All Articles