قم بتشغيل شاشة TFT Transflective على SSD1283A باستخدام STM32

مقدمة


يسمى نموذج العرض H016IT01. هذا العرض مثير للاهتمام في المقام الأول لأنه يعكس. هذا يعني أن الصورة عليها يجب أن تكون مرئية حتى تحت أشعة الشمس الساطعة. وهذا أيضًا هو النموذج الوحيد المتاح مع هذه الميزة على الموقع الصيني الشهير.

ولكن تم نشر المقال نظرًا لوجود معلومات قليلة جدًا عن وحدة التحكم SSD1283A (في كلا الجزءين الروسي والغربي من الشبكة) ، ولم أر دليلًا في أي مكان. يمكنك العثور على ورقة البيانات على الشبكة ، ولكن لا توجد معلومات حول التهيئة والعمل مع العرض ، وأوصاف السجلات فقط هي مفيدة.

أريد أن أؤكد أن هذه المادة هي بالتأكيد ليست الحقيقة المطلقة. أعطي فقط تجربتي في التفاعل مع الجهاز. الهدف الرئيسي من هذه المقالة بسيط - لمساعدة جميع أولئك الذين يقررون أو يريدون أو يريدون العمل مع هذا العرض ، لا أكثر.

صورة

العرض لديه 8 دبابيس:

1) GND
2) VCC - 5 أو 3.3V
3) CS - SPI رقاقة حدد
4) RST - "0" - إيقاف تشغيل الشاشة ، "1" - يتم تشغيله.
5) A0 / DC - أمر البيانات ("0" - الأمر ، "1" - البيانات)
6) SDA - SPI MOSI
7) SCK - SPI SCK
8) الصمام - الناتج الإضاءة الخلفية ، مثل VCC ، من 3.3 إلى 5V

تحتاج إلى برمجة الشاشة باستخدام SPI ، فإن لوحة الاكتشاف في stm32f407 ستساعدني في ذلك.

SPI


على الرغم من أنني تفاعلت مع SSD1283A عبر SPI ، تجدر الإشارة إلى أن وحدة التحكم توفر أيضًا واجهة متوازية ، لكن طراز العرض هذا لا يدعمها. SPI له هو أيضا غير عادي ، لديه خط بيانات SDA واحد فقط. في الواقع ، هذا هو خط MOSI ، مما يعني أنه لا يمكننا حساب أي شيء من الشاشة ، التي تخبرنا بها ورقة البيانات.

صورة

أولاً ، سنقوم بتكوين SPI ، لذلك سنهاجم SPI1 و GPIO ، ونقوم بتهيئة أرجل SDA و SCK كدالة بديلة (كما فعلت MISO ، لكن هذا ليس ضروريًا). يتم تكوين وضع التشغيل كسيد مرسل أحادي الاتجاه.

BIT_BAND_PER(RCC->AHB1ENR ,RCC_AHB1ENR_GPIOAEN) = true; if(SPI_NUM::_1 == spi) { /*!<-------------Enable spi clocking--------------->!*/ BIT_BAND_PER(RCC->APB2ENR, RCC_APB2ENR_SPI1EN) = true; /*!<----PA5 - SCK, PA6 - MISO, PA7 - MOSI---->!*/ GPIOA->MODER |= (GPIO_MODER_MODE5_1 | GPIO_MODER_MODE6_1 | GPIO_MODER_MODE7_1); GPIOA->OSPEEDR |= (GPIO_OSPEEDR_OSPEED5_1 | GPIO_OSPEEDR_OSPEED7_1); GPIOA->AFR[0] |= (GPIO_AFRL_AFSEL5_0 | GPIO_AFRL_AFSEL5_2 | GPIO_AFRL_AFSEL6_0 | GPIO_AFRL_AFSEL6_2 | GPIO_AFRL_AFSEL7_0 | GPIO_AFRL_AFSEL7_2); /*!<-----customize SPI------>!*/ SPI1->CR1 |= (SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI /*| SPI_CR1_DFF*/ | SPI_CR1_MSTR); BIT_BAND_PER(SPI1->CR1 ,SPI_CR1_SPE) = true; } 


ثم نكتب وظيفة بسيطة لنقل البايت بواسطة SPI:
 void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; } 

هذا يكفي لتشغيل SPI في الوضع الذي نحتاج إليه ونقل البيانات بأعلى سرعة ممكنة.

عرض التهيئة


لقد حان الوقت لتهيئة العرض. للقيام بذلك ، أرسل سلسلة معينة من الأوامر. يتكون الأمر من 3 بايت ، حيث يكون بايت واحد هو رقم الأمر ، والاثنان الآخران عبارة عن بيانات. تحتاج أيضًا إلى تبديل رقم التعريف الشخصي A0 عند إرسال بايت الأمر إلى "0" ، وللبيانات إلى "1". للراحة ، قمت بإجراء وظائف مضمّنة لتبديل حالة دبابيس RST و A0 و CS في الشاشة.

 enum class DC : uint8_t { COMMAND, DATA }; #pragma inline=forced inline void tft_lcd_rst(bool rst) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD2) = rst;} #pragma inline=forced inline void tft_lcd_dc(DC dc) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD3) = static_cast<bool>(dc);} #pragma inline=forced inline void tft_lcd_cs(bool cs) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD4) = cs;} 


سيظهر سطر الأوامر هكذا:
 void tft_lcd::tft_lcd_send(uint8_t addr, uint16_t data) { this->tft_lcd_dc(DC::COMMAND); stm32f407_spi_send(addr); this->tft_lcd_dc(DC::DATA); stm32f407_spi_send(static_cast<uint8_t>(data >> 8)); stm32f407_spi_send(static_cast<uint8_t>(data)); } 

للتهيئة ، يلزمك تقديم تسلسل الأوامر التالي ، والذي كنت أتجسسه في مكتبات arduino لهذه الشاشة:

أوامر التهيئة
 static constexpr uint8_t TFT_DELAY = 0xFF; static constexpr t_tft_regs tft_regs[]= { { 0x10, 0x2F8E }, { 0x11, 0x000C }, { 0x07, 0x0021 }, { 0x28, 0x0006 }, { 0x28, 0x0005 }, { 0x27, 0x057F }, { 0x29, 0x89A1 }, { 0x00, 0x0001 }, { TFT_DELAY, 100 }, { 0x29, 0x80B0 }, { TFT_DELAY, 30 }, { 0x29, 0xFFFE }, { 0x07, 0x0223 }, { TFT_DELAY, 30 }, { 0x07, 0x0233 }, { 0x01, 0x2183 }, { 0x03, 0x6830 }, { 0x2F, 0xFFFF }, { 0x2C, 0x8000 }, { 0x27, 0x0570 }, { 0x02, 0x0300 }, { 0x0B, 0x580C }, { 0x12, 0x0609 }, { 0x13, 0x3100 }, }; 


حيث TFT_DELAY يعني النوم البسيط بالقدر المحدد من mS. إذا بحثت في ورقة البيانات ، فستبدو هذه البيانات لبعض العناوين غريبة ، نظرًا لأن العديد من العناوين تكتب إلى وحدات بت محفوظة.

للتهيئة ، اسمح "0" بـ CS ، بإعادة تشغيل الشاشة (دبوس RST) ، وانتقل إلى جدول الأوامر.

  tft_lcd_rst(false); delay(5, mS); tft_lcd_rst(true); delay(200, mS); this->tft_lcd_cs(false); delay(5, mS); /*!<--------Init display---------->!*/ for(uint8_t i = 0; i < sizeof(tft_regs)/sizeof(tft_regs[0]) ;i++) { (TFT_DELAY != tft_regs[i].address) ? (this->tft_lcd_send(tft_regs[i].address, tft_regs[i].value)): (delay(tft_regs[i].value, mS)); } delay(5, mS); this->tft_lcd_cs(true); 

بعد ذلك ، يجب أن تغير الصورة الصورة من التداخل التلفزيوني الأبيض إلى اللون الرمادي.

صورة

ارسم مستطيلاً


تسمح لك وحدة التحكم SSD1283A برسم الصور مع المستطيلات ، والتي تستخدم 4 أوامر. يحتوي الأمر 0x44 على إحداثي نهاية وبداية المستطيل على طول محور الإحداثي في ​​بايت البيانات العالية والمنخفضة ، على التوالي. الأمر 0x45 هو نفسه بالنسبة لمحور الإحداثيات. يحتوي الأمر 0x21 على إحداثيات العرض الأولي ، في البايت العالي لـ y ، في البايت المنخفض لـ x. يحتوي الأمر 0x22 على لون البكسل الحالي. هذا يعني أنه يحتاج إلى تكرار لكل بكسل من المستطيل الحالي. تتميز الشاشة أيضًا بميزة ، على الرغم من أن دقة الشاشة نفسها 130 × 130 ، فإن شبكة الإحداثيات الافتراضية لها أبعاد 132 × 132 ، وتبدأ الإحداثيات في العد من النقطة 2 × 2.

لذلك ، على سبيل المثال ، إذا أردنا رسم مربع أسود 20 بحلول 20 ، ونقطة انطلاقه في الموضع (30 ، 45) ، فإننا نحتاج إلى إرسال تسلسل الأوامر التالي:

0x44 0x3320 (30 + 20 + 2-1 ، 30 + 2)
0x45 0x422F (45 + 20 + 2-1 ، 45 + 2)
0x21 0x2F20
0x22 0x0000 ، ويجب إرسال هذا الأمر 400 (20 * 20) مرة.

عندئذٍ ستبدو وظيفة رسم المستطيل هكذا (بشرط أن تكون الإحداثيات قد تحولت بالفعل بمقدار 2):

 void tft_lcd::draw_rect(const t_rect& rec) { this->tft_lcd_send(0x44, ((rec.x1 - 1) << 8) | rec.x0); this->tft_lcd_send(0x45, ((rec.y1 - 1) << 8) | rec.y0); this->tft_lcd_send(0x21, (rec.y0 << 8) | rec.x0); for(uint16_t i = 0; i < ((rec.x1 - rec.x0) * (rec.y1 - rec.y0)); i++) { this->tft_lcd_send(0x22, rec.col); } } 

لرسم مستطيل ، ما عليك سوى تحديد إحداثيات زواياها ولونها. مثال على ملء الشاشة بالكامل باللون الوردي سيبدو كما يلي:

 t_rect rect = {0x02, 0x02, 0x84, 0x84, static_cast<uint16_t>(COLOUR::MAGENTA)}; this->draw_rect(rect); 

النتيجة:

صورة

ارسم الحروف والأرقام


إنشاء قائمة صفائف مع إحداثيات للشخصيات.

صفيف إحداثيات الحروف والأرقام
 namespace rect_coord_lit { const t_rect_coord comma[] = {{0, 20, 5, 25}, {3, 25, 5, 28}}; const t_rect_coord dot[] = {{0, 20, 5, 25}}; const t_rect_coord space[] = {{0, 0, 0, 0}}; const t_rect_coord _0[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 20, 15, 25},{10, 5, 15, 20}}; const t_rect_coord _1[] = {{10, 0, 15, 25}}; const t_rect_coord _2[] = {{0, 0, 15, 5},{10, 5, 15, 15},{0, 10, 10, 15},{0, 15, 5, 25},{5, 20, 15, 25}}; const t_rect_coord _3[] = {{0, 0, 15, 5},{10, 5, 15, 25},{0, 10, 10, 15},{0, 20, 10, 25}}; const t_rect_coord _4[] = {{0, 0, 5, 15},{5, 10, 10, 15},{10, 0, 15, 25}}; const t_rect_coord _5[] = {{0, 0, 15, 5},{0, 5, 5, 15},{0, 10, 15, 15},{10, 15, 15, 25},{0, 20, 10, 25}}; const t_rect_coord _6[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 10, 10, 15},{5, 20, 10, 25},{10, 10, 15, 25}}; const t_rect_coord _7[] = {{0, 0, 15, 5},{10, 5, 15, 25}}; const t_rect_coord _8[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 20, 15, 25},{10, 5, 15, 20},{5, 10, 10, 15}}; const t_rect_coord _9[] = {{0, 0, 15, 5},{0, 5, 5, 15},{0, 20, 15, 25},{10, 5, 15, 20},{5, 10, 10, 15}}; const t_rect_coord a[] = {{0, 10, 5, 25},{5, 5, 10, 10},{5, 15, 10, 20},{10, 10, 15, 25}}; const t_rect_coord b[] = {{0, 0, 5, 25},{5, 10, 15, 15},{10, 15, 15, 20},{5, 20, 15, 25}}; const t_rect_coord c[] = {{0, 5, 15, 10},{0, 10, 5, 20},{0, 20, 15, 25}}; const t_rect_coord d[] = {{0, 10, 10, 15},{0, 15, 5, 20},{0, 20, 10, 25}, {10, 0, 15, 25}}; const t_rect_coord e[] = {{0, 5, 15, 8}, {0, 12, 15, 15}, {0, 8, 5, 25}, {10, 8, 15, 12}, {5, 20, 15, 25}}; const t_rect_coord f[] = {{5, 5, 10, 25},{5, 0, 15, 5},{0, 10, 15, 15}}; const t_rect_coord g[] = {{0, 5, 5, 20}, {5, 5, 10, 10}, {5, 15, 10, 20}, {10, 5, 15, 30}, {0, 25, 10, 30}}; const t_rect_coord h[] = {{0, 0, 5, 25},{5, 10, 15, 15},{10, 15, 15, 25}}; const t_rect_coord i[] = {{5, 3, 10, 8},{5, 10, 10, 25}}; const t_rect_coord j[] = {{5, 3, 10, 8},{5, 10, 10, 30}, {0, 25, 5, 30}}; const t_rect_coord k[] = {{0, 0, 5, 25},{5, 15, 10, 20}, {10, 10, 15, 15}, {10, 20 , 15, 25}}; const t_rect_coord l[] = {{5, 0, 10, 25}}; const t_rect_coord m[] = {{0, 10, 4, 25},{7, 10, 10, 25}, {13, 10, 17,25}, {0, 5 , 12, 10}}; const t_rect_coord n[] = {{0, 10, 5, 25},{10, 10, 15, 25}, {0, 5 , 10, 10}}; const t_rect_coord o[] = {{0, 5, 5, 25}, {10, 5, 15, 25}, {5, 5, 10, 10}, {5, 20, 10, 25}}; const t_rect_coord p[] = {{0, 5, 5, 30}, {5, 5, 15, 10}, {5, 15, 15, 20}, {10, 10, 15, 15}}; const t_rect_coord q[] = {{0, 5, 5, 20}, {5, 5, 15, 10}, {5, 15, 15, 20}, {10, 10, 15, 30}}; const t_rect_coord r[] = {{0, 10, 5, 25},{5, 5, 15, 10}}; const t_rect_coord s[] = {{3, 5, 15, 10}, {0, 8, 5, 13}, {3, 13, 12, 17}, {10, 17, 15, 22}, {0, 20, 12, 25}}; const t_rect_coord t[] = {{5, 0, 10, 25},{0, 5, 15, 10},{10, 20, 15, 25}}; const t_rect_coord u[] = {{0, 5, 5, 25},{10, 5, 15, 25},{5, 20, 10, 25}}; const t_rect_coord v[] = {{0, 5, 5, 15}, {10, 5, 15, 15}, {1, 15, 6, 20}, {9, 15, 14, 20}, {5, 20, 10, 25}}; const t_rect_coord w[] = {{0, 5, 4, 20},{7, 5, 10, 20}, {13, 5, 17, 20}, {4, 20 , 7, 25}, {10, 20 , 13, 25}}; const t_rect_coord x[] = {{0, 5, 5, 10},{10, 5, 15, 10}, {0, 20, 5, 25}, {10, 20 , 15, 25}, {5, 10 , 10, 20}}; const t_rect_coord y[] = {{0, 5, 5, 20}, {5, 15, 10, 20}, {10, 5, 15, 30}, {0, 25, 10, 30}}; const t_rect_coord z[] = {{0, 5, 15, 10}, {10, 10, 15, 13}, {5, 12, 10, 18}, {0, 17, 5, 20}, {0, 20, 15, 25}}; } 


قم بإنشاء جدول حيث نربط الحرف نفسه (في ascii) ، وعدد المستطيلات والإحداثيات:

تنسيق جدول المؤشر
 typedef struct { char lit; uint8_t size; const t_rect_coord *rec_coord; }t_rect_coord_table; #define LITERAL_COORD(x) sizeof(x)/ sizeof(x[0]), x const t_rect_coord_table rect_coord_table[] = { {',', LITERAL_COORD(rect_coord_lit::comma)}, {'.', LITERAL_COORD(rect_coord_lit::dot)}, {'.', LITERAL_COORD(rect_coord_lit::dot)}, {' ', LITERAL_COORD(rect_coord_lit::space)}, {'0', LITERAL_COORD(rect_coord_lit::_0)}, {'1', LITERAL_COORD(rect_coord_lit::_1)}, {'2', LITERAL_COORD(rect_coord_lit::_2)}, {'3', LITERAL_COORD(rect_coord_lit::_3)}, {'4', LITERAL_COORD(rect_coord_lit::_4)}, {'5', LITERAL_COORD(rect_coord_lit::_5)}, {'6', LITERAL_COORD(rect_coord_lit::_6)}, {'7', LITERAL_COORD(rect_coord_lit::_7)}, {'8', LITERAL_COORD(rect_coord_lit::_8)}, {'9', LITERAL_COORD(rect_coord_lit::_9)}, {'a', LITERAL_COORD(rect_coord_lit::a)}, {'b', LITERAL_COORD(rect_coord_lit::b)}, {'c', LITERAL_COORD(rect_coord_lit::c)}, {'d', LITERAL_COORD(rect_coord_lit::d)}, {'e', LITERAL_COORD(rect_coord_lit::e)}, {'f', LITERAL_COORD(rect_coord_lit::f)}, {'g', LITERAL_COORD(rect_coord_lit::g)}, {'h', LITERAL_COORD(rect_coord_lit::h)}, {'i', LITERAL_COORD(rect_coord_lit::i)}, {'j', LITERAL_COORD(rect_coord_lit::j)}, {'k', LITERAL_COORD(rect_coord_lit::k)}, {'l', LITERAL_COORD(rect_coord_lit::l)}, {'m', LITERAL_COORD(rect_coord_lit::m)}, {'n', LITERAL_COORD(rect_coord_lit::n)}, {'o', LITERAL_COORD(rect_coord_lit::o)}, {'p', LITERAL_COORD(rect_coord_lit::p)}, {'q', LITERAL_COORD(rect_coord_lit::q)}, {'r', LITERAL_COORD(rect_coord_lit::r)}, {'s', LITERAL_COORD(rect_coord_lit::s)}, {'t', LITERAL_COORD(rect_coord_lit::t)}, {'u', LITERAL_COORD(rect_coord_lit::u)}, {'v', LITERAL_COORD(rect_coord_lit::v)}, {'w', LITERAL_COORD(rect_coord_lit::w)}, {'x', LITERAL_COORD(rect_coord_lit::x)}, {'y', LITERAL_COORD(rect_coord_lit::y)}, {'z', LITERAL_COORD(rect_coord_lit::z)} }; 


عندها ستبدو وظائف الرسم للشخصية التالية:

 void tft_lcd::draw_lit(char ch, const t_rect_coord *rect_coord, uint16_t colour, uint16_t x0, uint16_t y0, uint8_t size) { t_rect rec = {0}; rec.col = colour; uint8_t ctr = size; uint8_t i = 0; while(ctr--) { rec.x0 = x0 + rect_coord[i].x0; rec.y0 = y0 + rect_coord[i].y0; rec.x1 = x0 + rect_coord[i].x1; rec.y1 = y0 + rect_coord[i].y1; i++; this->draw_rect(rec); } } void tft_lcd::draw_char(char ch, uint16_t colour, uint16_t x0, uint16_t y0) { x0 += 2; y0 +=2; for(const auto &field : rect_coord_table) { if(field.lit == ch) { draw_lit(ch, field.rec_coord, colour, x0, y0, field.size); } } } 

تمثل وظيفة draw_char آلة حالة محدودة ، حيث تمر حلقة for خلال كل حقل من جدول الإحداثيات وتبحث عن تطابق برمز. إذا تم العثور على تطابق ، يتم نقل البيانات إلى وظيفة draw_lit ، والتي ترسم عدد المستطيلات المطلوب عند الإحداثيات المحددة.

سيبدو رسم الخط هكذا:
 void tft_lcd::draw_string(char *ch, COLOUR colour, uint16_t x0, uint16_t y0) { while(*ch) { this->draw_char(*ch, static_cast<uint16_t>(colour), x0, y0); x0+= 20; ch++; } } 

الآن يكفي ضبط الخط وموضعه ولونه المبدئي. لإضافة رسائل جديدة ، ما عليك سوى إنشاء مجموعة من الإحداثيات وإضافتها إلى الجدول. مثال على الخطوط العشوائية عن طريق الكود:

 this->draw_string("123456", COLOUR::GREEN, 5, 0); this->draw_string("habr,.", COLOUR::WHITE, 5, 30); this->draw_string("abcdef", COLOUR::RED, 5, 60); this->draw_string("stwxyz", COLOUR::YELLOW, 5, 90); 

صورة

في هذا الوضع ، تكون الشاشة سريعة ، ولا تلاحظ العين عملية الرسم.

لجميع المهتمين ، يمكن الاطلاع على رمز المشروع الكامل على الرابط .

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


All Articles