Когда есть только дисплей и больше ничего

Начитался я как-то разных статей, заметок, форумов про то, как ребята подключают дисплеи от мобильных телефонов и тоже решил попробовать. Интересно ведь. Специально для опытов были куплены образцы дисплеев. Про часть из них я рассказал в своей прошлой статье. Но первыми были не они…Сегодня я поделюсь опытом разгадывания своего первого дисплея и его оживления.

024cd2c4c161d82ddcf4fa2a6716a72e.jpg

В прошлый раз я описывал процесс обратной разработки неизвестного дисплея, в том числе, и с помощью логического анализатора. Чтобы такой фокус получился нужен сам анализатор и рабочее устройство, тогда можно записать обмен, а потом с ним уже работать. Но далеко не всегда удаётся найти рабочее тело, да и анализатор есть не у всех. Поэтому в данной статье посмотрим, как быть, когда есть только дисплей и больше ничего.

Что приходит в голову, когда в руках дисплей от мобильного телефона, модель которого известна? Во-первых, как и в любом случае, поискать, может кто-то его уже подключил. В данном случае дисплей от телефона ZTE R221. По крайней мере, так сообщил продавец с барахолки. После недолгих поисков я понял, что подключать, видимо, придётся самому. Не беда — поищем схему телефона. Обычно я ищу по ключевым словам «схема», «schematic», «sch», «service manual», «repair manual» или как-то ещё. Также иногда хорошо поискать на тематических сайтах, где выкладывают множество мануалов для разной техники. После всего можно попытать счастья на китайских сайтах. Благо есть переводчик гугл. С китайскими сайтами я пока не сильно освоился, но там бывает много интересного. А вы как ищете информацию? Поделитесь в комментариях.

Может я не умею искать, но схему я так и не нашёл. Немного отвлекусь и скажу, что поиск информации — это ключ к успеху. А ещё это целое искусство. Пока пробовал разгадывать экраны (и не только) многому научился в смысле поиска, но также ещё лучше почувствовал, что я ещё очень многого не умею.

Ладно, нет схемы, так нет. Соберём побольше информации. Что у нас есть? Известна модель телефона, посмотрим характеристики экрана телефона. Разрешение 160×128 (1.8»), TFT, 65536 цв. Также у нас в руках есть сам дисплей, а это очень много.

a45b27c1e443ab1f287dfd8b8eddbaef.jpgЕщё фото экрана

7202af9eba243fde52328a1aa1ec2ab7.jpg

На шлейфе видим надпись BTL181216–358L. Пробуем забить в поиск, авось повезёт. Хотя часто это бесполезно. Среди прочих выдало сайт panelook. Правда экран тут не точно такой, как у нас, а немного другой, BTL181216–353L. Код отличается лишь на одну цифру, кол-во контактов другое (здесь 35, а у нас 20). Самое интересное на этой странице — это фраза «Built-in ST7735R driver IC ». Одна цифра, да ещё в конце кода — это немного, поэтому имеет смысл предположить, что у нас такой же драйвер. Сразу же качаем даташит на ST7735R.

Подозреваемый обнаружен, осталось определить распиновку. Как обычно, сперва ищем самое простое. Вы тоже любите земляной контакт? Земля — это наш друг. Она есть всегда и везде, и её просто найти (по крайней мере мне иное не попадалось). Далее ищем подсветку. Всё как в прошлой статье. Судя по количеству контактов у нас снова 8 битный интерфейс 8080. Далее по предварительной распиновке пробуем найти полную. Запросы приводят, в основном, на алиэкспрес, но имено такого экранчика как у нас нет. Зато есть похожие на ST7735R, но с другим количеством контактов.

Тут мне не хотелось долго искать распиновку в интернете, и я решил посмотреть, что есть в даташите. А там у нас есть распиновка чипа на странице 5 (Chip Information). Там же приводятся его размеры. Иногда это помогает отсеять неподходящие драйверы, просто измеряя их длину и ширину. Теперь по расположению выводов микросхемы можно найти где какой контакт на шлейфе. Всё бы хорошо, только вот на шлейфе 79 контактов (около микросхемы, дорожки выведены не от всех), а у самой микросхемы 185 (со стороны шлейфа) На шлейфе даже есть надписи »1» и »79». Очевидно, это номера контактов. Рассматривая дорожки, выходящие из микросхемы, под самодельным микроскопчиком, я никак не мог чётко понять, что куда идёт.

Не зная что делать, я решил поискать распиновку в интернете ещё раз. Пересматривая похожие экраны на али (с драйвером ST7735R), я обратил внимание, что фото довольно качественные и на них чётко видны дорожки.

Дисплей ST7735 14 pin SPI верх Дисплей ST7735 14 pin SPI верх

И с обратной стороны:

Дисплей ST7735 14 pin SPI низ Дисплей ST7735 14 pin SPI низ

Картинки, приведенные выше уже обрезаны — выделена интересующая область. Добрый продавец привёл также распиновку. Совмещаем фото, подписываем контакты.

2dbb35a55ada466e110e7aec6013cfe3.png

Почему-то рисовал всё это я в paint, хотя удобнее было бы, пожалуй, в фотошопе. По тому, какие контакты используются в 14-выводной версии можно понять, что здесь 3-Line serial MCU Interface (стр 163 даташита). (Картинку лучше отрыть в новой вкладке и там увеличить.)

Если внимательно посчитать количество дорожек, идущих от микросхемы на шлейф, у 14-выводного экрана, то обнаружим, что их 24. А у 20-выводного экрана (т. е. у нашего) их 31. 31–24=7. А нам как раз и не хватает линий D1…D7. Тут поясню: на схеме соединений для интерфейса 3-Line serial (на картинке выше это есть) видим, что вывод D0 у нас уже есть (он же SDA), а выводы D1…D17 не используются, поэтому заземлены. А так как они расположены рядом, то от них идёт одна дорожка на земляной полигон, а не 17.

Также обратим внимание на выводы выбора интерфейса IM0…IM2, SPI4W. Они тоже заземлены. Дорожки от них тоже должны идти на шлейф. Снова просматривая полную распиновку чипа, замечаем, что вывод IM2 (заземлён, т. к. 3-Line serial) находится рядом с выводом D0 (не заземлён, т. к. это вывод SDA). Так, так, а куда нужно подключать IM2, чтобы включился режим 8 бит? Давайте-ка посмотрим. Находим в даташите такую картинку.

edd4d2f4c44e026a7f7cb72280bc071d.png

Вот оно! По выводу IM2 мы теперь вычислим все остальные, но уже на нашем 20-выводном экране. Рисуем дальше, опираясь на 14-выводной экран и на расположение выводов микросхемы (картинку снова лучше открыть в новой вкладке и увеличить).

f5b245e12bcdf4de7eae1af0ebe9929d.png

Выглядит страшно, но рисовать это было не сложно и не долго. В принципе, в пэинте тоже можно работать. Картинки не редактировал, а выложил в том виде, как оно было.

В даташите также находим, что у микросхемы 2 отдельных вывода питания — VDD, VDDIO. Определить где VDDIO просто — к нему в режиме 8 бит подключается IM2. Также сразу глянем, какое напряжение нужно подавать на выводы питания.

Получилась такая распиновка

Контроллер ST7735R
1- GND
2- /Reset
3- DC
4- WR
5- RD
6- D0
7- D1
8- D2
9- D3
10- D4
11- D5
12- D6
13- D7
14- CS
15- VDDIO (+) (1.65V~3.7V (VDDI ≤ VDD))
16- Конец резистора, подцепленного другим выводом к VDDIO. Видимо для идентификации модели экрана
17- VDD (+) (2.3V~4.8V)
18- A (LED+) (к +3.3В через резистор)
19- K (LED-)
20- GND

Подключить экранчик можно и к Ардуино, т. к. библиотек для ST7735R хватает. К Ардуино нужно подключать через преобразователь логических уровней, т. к. 5 В для этого экрана много. Я использовал преобразователь, который нашёл как-то на этом сайте. Проект мне понравился и я решил его повторить, немного изменив под свои детальки. Между прочим, там много чего есть интересного по дисплеям, по обратной разработке и прочему.

У меня получилась такая платка

aba959853f2ad16c4d85ce02b4d78f72.jpgd0ce7b2066536478dd1b6d24ecda1464.jpg

Не помню уже точно, какой я тогда использовал скетч. Это была какая-то демо программа для ST7735R по 8-битной шине. Пробовал запустить другой скетч — экран ожил, но показывал как-то криво. В итоге я решил просто переделать на stm32. Экран показал следующее:

4c290c23d47f797148d61584554b85d1.jpgНа всякий случай код для stm32f103c8t6

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * 

© Copyright (c) 2022 STMicroelectronics. * All rights reserved.

* * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stm32f1xx_ll_gpio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ // LCD_RST #define LCD_RST1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0,GPIO_PIN_SET); #define LCD_RST0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0,GPIO_PIN_RESET); // LCD_DC #define LCD_DC1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1,GPIO_PIN_SET); #define LCD_DC0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1,GPIO_PIN_RESET); // LCD_WR #define LCD_WR1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2,GPIO_PIN_SET); #define LCD_WR0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2,GPIO_PIN_RESET); // LCD_RD #define LCD_RD1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3,GPIO_PIN_SET); #define LCD_RD0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3,GPIO_PIN_RESET); // LCD_CS #define LCD_CS1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4,GPIO_PIN_SET); #define LCD_CS0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4,GPIO_PIN_RESET); //Connect d0...d7 reset DC WR RD CS // to b3...b10 A0 A1 A2 A3 A4 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ void lcd_sendbyte(uint8_t byte)//8080 8 bit protocol { LCD_CS0; LCD_WR0; //lcd d0 connected to pin b3, d1 to d4, that is why byte shifted by 3 ... LL_GPIO_WriteOutputPort(GPIOB, byte<<3); LCD_WR1;//The display writes D[17:0] lines when there is a rising edge of WRX LCD_CS1; } void lcd_sendcmd(uint8_t cmd) { LCD_DC0; lcd_sendbyte(cmd); } void lcd_senddata(uint8_t data) { LCD_DC1; lcd_sendbyte(data); } void lcd_draw_pixel(uint8_t x, uint8_t y, uint16_t color) { lcd_sendcmd(0x2A);//Column adress set lcd_senddata(0x00); lcd_senddata(x); lcd_sendcmd(0x2B);//Row Address Set lcd_senddata(0x00); lcd_senddata(y); lcd_sendcmd(0x2C);//Memory Write lcd_senddata((color & 0xFF00)>>8);//higher byte lcd_senddata(color & 0x00FF);//lower byte } void lcd_at(uint8_t start_x, uint8_t start_y, uint8_t stop_x, uint8_t stop_y) { lcd_sendcmd(0x2A); lcd_senddata(0x00); lcd_senddata(start_x); lcd_senddata(0x00); lcd_senddata(stop_x); lcd_sendcmd(0x2B); lcd_senddata(0x00); lcd_senddata(start_y); lcd_senddata(0x00); lcd_senddata(stop_y); } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ //here my functions //inicializaciya LCD_RD1; LCD_CS0; // CS=0 LCD_RST0; // RST=0 HAL_Delay(120); LCD_RST1; // RST=1 HAL_Delay(120); lcd_sendcmd(0x11); // Sleep Out & Booster On HAL_Delay(120); // lcd_sendcmd (0x3A); // Color mode (Interface Pixel Format) lcd_senddata(0x05); // 16-bit/pixel //lcd_sendcmd (0x20); // Display inversion off lcd_sendcmd (0x21); // Display inversion on lcd_sendcmd (0x29); // Display On unsigned char y; unsigned char x; // for (y=0;y<160;y++) { for (x=0;x<128;x++) { uint16_t color=0x0; if (y<53) color=0xF800; //RED else if (y<106) color=0x07E0; //GREEN else if (y<160) color=0x001F; //BLUE lcd_draw_pixel(x, y, color); } } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* for (y=0;y<160;y++) { for (x=0;x<128;x++) { uint16_t color=0x0;//don`t what is this color, but drawn white lcd_draw_pixel(x, y, color); } } HAL_Delay(120); lcd_at(0, 0, 128, 160); lcd_sendcmd(0x2C); for (y=0;y<160;y++) { for (x=0;x<128;x++) { uint16_t color=0x0000; if (y<40) color=0xFF00; // else { if (y<80) color=0x02E0; // Green else { if (y<120) color=0x001F; } // } lcd_senddata((color & 0xFF00)>>8);//higher bits lcd_senddata(color & 0x00FF);//lower bits } } HAL_Delay(120); lcd_at(0, 0, 128, 160); lcd_sendcmd(0x2C); for (y=0;y<160;y++) { for (x=0;x<128;x++) { uint16_t color=0xFF00; lcd_senddata((color & 0xFF00)>>8);//higher bits lcd_senddata(color & 0x00FF);//lower bits } } */ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3 |GPIO_PIN_4, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET); /*Configure GPIO pins : PA0 PA1 PA2 PA3 PA4 */ GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3 |GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : PB10 PB3 PB4 PB5 PB6 PB7 PB8 PB9 */ GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief Period elapsed callback in non blocking mode * @note This function is called when TIM2 interrupt took place, inside * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment * a global variable "uwTick" used as application time base. * @param htim : TIM handle * @retval None */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* USER CODE BEGIN Callback 0 */ /* USER CODE END Callback 0 */ if (htim->Instance == TIM2) { HAL_IncTick(); } /* USER CODE BEGIN Callback 1 */ /* USER CODE END Callback 1 */ } /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

Также попробовал замечальную библиотеку. Хотелось быстро подключить, особо не разбираясь с кодом (там много), поэтому быстренько повторил всё как здесь, только для своего случая, и всё опять заработало. Отличная библиотека.

Можно ещё попробовать считать ID драйвера экрана но, сейчас это незачем, ведь экран уже ожил и показывает. Но если есть распиновка, а модель контроллера неизвестна, то это может здорово помочь. Правда, не все контроллеры такое умеют.

А ещё целая живая ветка на форуме, где выкладывают множество материалов по экранам (большим в том числе) и даже описывают процесс обратной разработки. Там же есть «читалка», которая считывает ID контроллера и запускает демо тест.

Итог

Сегодня я показал свой опыт обратной разработки экрана без использования логического анализатора и прочего. В какой-то момент казалось, что этот дисплей мне подключить не удастся. Но я решил попробовать снова и решение пришло самым неожиданным образом. Главное не унывать, но действовать. Не думать «у меня опять не получается, всё плохо», а «что я могу сделать?» или «с чего бы начать?». Действуйте с добрыми намерениями, и у вас тоже всё получится. И как же всё таки круто, когда удаётся решить эту довольно непростую задачу, и дисплей оживает! Всем удачных проектов!

© Habrahabr.ru