نحن أصدقاء STM32 مع شاشة LCD 1604 على حافلة I2C (مكتبة HAL)

مرحبا يا هابر!

في هذه المقالة ، أود أن أتحدث عن تجربتي في توصيل شاشات LCD إلى متحكم STM32 باستخدام مكتبة HAL على ناقل I2C.

صورة

سأقوم بتوصيل الشاشة 1602 و 2004. كلاهما يحتوي على محول I2C ملحوم على أساس رقاقة PCF8574T. ستكون لوحة التصحيح Nucleo767ZI ، وستكون بيئة التطوير STM32CubeIDE 1.3.0.

لن أتحدث بالتفصيل عن مبدأ تشغيل الحافلات I2C، أنصحك أن ننظر هنا و هنا .

نقوم بإنشاء المشروع ، حدد لوحة التصحيح: نشير

صورة

إلى أننا سنستخدم I2C1. سأقوم أيضًا بتوصيل UART5 للتواصل مع اللوحة ، وهذا ضروري لتلقي معلومات من اللوحة حول عنوان العرض.

صورة

صورة

في نفس النافذة ، يمكنك رؤية أرقام الأرجل التي تتصل بها الشاشة ، في حالتي ، تحولت إلى ما يلي:

صورة

للبدء ، قم بتوصيل شاشة واحدة فقط ، سأبدأ بـ 1602. أيضًا ، سأقوم بتوصيل محول USB-UART CH340 ، المعروف لسائقي arduin ذوي الخبرة ، لتلقي البيانات من اللوحة.

صورة

يرجى ملاحظة أن المحول يربط RX بـ TX و TX إلى RX ، فإن وصلة العبور على المحول هي 3.3V

صورة

دعونا نلقي نظرة فاحصة على العمل مع رقاقة PCF8574T والشاشة. فيما يلي رسم تخطيطي لوحدة نمطية مع شاشة عرض:

صورة

تشبه رقاقة PCF8574T في وظيفتها سجل التحول 74hc595 - حيث تستقبل وحدات البايت عبر واجهة I2C وتعين قيم البت المطابق لمخرجاتها (P0-P7).

ضع في اعتبارك دبابيس الدائرة المصغرة المتصلة بالشاشة وما هي مسؤوليتها:

  • يتم توصيل الطرف P0 للدائرة الدقيقة بطرف RS للشاشة ، المسؤولة عن استقبال بيانات العرض (1) أو تعليمات تشغيل العرض (0) ؛
  • يتم توصيل Pin P1 بـ R \ W ، إذا كان 1 - كتابة البيانات على الشاشة ، 0 - قراءة ؛
  • يتم توصيل المحطة الطرفية P2 بمحطة CS - المحطة ، عند قراءة الحالة التي تتم قراءتها ؛
  • الاستنتاج P3 - التحكم في الإضاءة الخلفية ؛
  • يتم استخدام الاستنتاجات P4 - P7 لإرسال البيانات إلى الشاشة.

يمكن توصيل العديد من الأجهزة بناقل I2C واحد في نفس الوقت. لكي نتمكن من الوصول إلى جهاز معين ، لكل منهم عنوانه الخاص ، سنكتشف أولاً. إذا لم يتم إغلاق جهات الاتصال A1 و A2 و A3 على لوحة المحول ، فمن المرجح أن يكون العنوان 0x27 ، ولكن من الأفضل التحقق منه. للقيام بذلك ، سنكتب وظيفة صغيرة تعرض عناوين جميع الأجهزة المتصلة بناقل I2C:

void I2C_Scan ()
{
	//  ,  
        HAL_StatusTypeDef res;                          
	//    
        char info[] = "Scanning I2C bus...\r\n";      
        //    UART	
        HAL_UART_Transmit(&huart5, (uint8_t*)info, strlen(info), HAL_MAX_DELAY);  
	/* &huart5 -   UART
	 * (uint8_t*)info -     
	 * strlen(info) -   
	 * HAL_MAX_DELAY - 
	 */
        //    
	for(uint16_t i = 0; i < 128; i++)              
	{
            // ,      i  
            res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, HAL_MAX_DELAY);                   
	    //  , 
            if(res == HAL_OK)                              
	    {
	    	char msg[64];
	    	//   i,   ,     
                // 16 :
	    	snprintf(msg, sizeof(msg), "0x%02X", i);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
	    }
	    else HAL_UART_Transmit(&huart5, (uint8_t*)".", 1, HAL_MAX_DELAY);
	}
	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
}

تستقصي هذه الوظيفة جميع العناوين من 0 إلى 127 ، وإذا تم تلقي استجابة من هذا العنوان ، فإنها ترسل رقم العنوان في شكل سداسي عشري إلى UART.

للتواصل مع المجلس ، أستخدم برنامج Termite. بشكل افتراضي ، يتم تعيين سرعة UART للميكروكونترولر إلى 115200 ، يجب عليك تعيين نفس السرعة في النمل الأبيض. نحن نطلق على الوظيفة في الجزء الرئيسي من البرنامج ، ونومض اللوح ونقوم بالاتصال باللمسي الأبيض بوحدة التحكم الدقيقة لدينا:

صورة

تعرض النقاط جميع العناوين التي لم يتم استلام الإجابة منها. العنوان على شاشتي هو 0x26 ، منذ أن قمت بلحام الطائر A0. الآن قم بتوصيل الشاشة الثانية الموازية للشاشة الأولى ، وانظر ما سيعطيه البرنامج:

صورة

لدينا عنوانان: 0x26 (عرض 1602) و 0x27 (عرض 2004). الآن حول كيفية العمل مع الشاشة. يرسل وحدة التحكم الدقيقة بايت العنوان ، وتقوم جميع الأجهزة المتصلة بالناقل بفحصها بنفسها. إذا تطابق ، تبدأ الوحدة في الاتصال بوحدة التحكم الدقيقة. بادئ ذي بدء ، تحتاج إلى تكوين العرض: من أين سيذهب عدد الأحرف ومن أي اتجاه ، وكيف سيتصرف المؤشر ، إلخ. بعد ذلك ، سيكون من الممكن بالفعل نقل المعلومات للعرض على الشاشة. تكمن الخصوصية في أنه لا يمكننا استخدام سوى 4 بتات لإرسال المعلومات ، أي يجب تقسيم البيانات إلى قسمين. يتم تخزين البيانات في البتات العالية (4-7) ، ويتم استخدام البتات المنخفضة للإشارة إلى ما إذا كان سيتم تشغيل الإضاءة الخلفية (3 بتات) ، وما إذا كانت البيانات قادمة للإخراج ، أو إعدادات العرض (خرج RS ، 0 بت) ، و 2 بت ، من خلال التغيير الذي تتم القراءة ، أيe لإرسال 1 بايت من البيانات تحتاج إلى إرسال 4 بايت - البايت الأول سيحتوي على 4 بت من المعلومات ، البتة الثانية للحالة 1 ، البايت الثاني هو تكرار للبايت الأول ، فقط البت الثاني للحالة 0. البايتان الثالث والرابع متشابهان ، فقط يحتويان النصف الثاني من البيانات. يبدو غير مفهوم قليلاً ، سأريكم مثالاً:

void I2C_send(uint8_t data, uint8_t flags)
{
	HAL_StatusTypeDef res;
	 //     
        for(;;) {
                // ,      lcd_addr                                                                                        
	        res = HAL_I2C_IsDeviceReady(&hi2c1, LCD_ADDR, 1, HAL_MAX_DELAY);               
	         //  ,     
                if(res == HAL_OK) break;                                                  
	    }
        //    1111 0000      0  3,    4  7
	uint8_t up = data & 0xF0;   
        //   ,  data   4                	
        uint8_t lo = (data << 4) & 0xF0;          
                                          
	uint8_t data_arr[4];
         // 4-7   ,  0-3   
	data_arr[0] = up|flags|BACKLIGHT|PIN_EN; 
         //  ,       0  
	data_arr[1] = up|flags|BACKLIGHT;         
	data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
	data_arr[3] = lo|flags|BACKLIGHT;

	HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_arr, sizeof(data_arr), HAL_MAX_DELAY);
	HAL_Delay(LCD_DELAY_MS);
}

لنأخذ الأمر بالترتيب. في البداية ، هناك متغيرات تخزن عنوان العرض ، وبتات من الإعدادات التي يجب إرسالها في كل مرة مع البيانات. في وظيفة الإرسال ، نتحقق أولاً من وجود وحدة نمطية في العنوان المسجل. في حالة تلقي رسالة HAL_OK ، نبدأ في تشكيل وحدات بايت للإرسال. في بداية البايت الذي سنرسله ، من الضروري التقسيم إلى قسمين ، وكتابة كلاهما إلى البتات العالية. لنفترض أننا نريد أن تعرض الشاشة الأحرف ، في النظام الثنائي يكون 1110011 ( الآلة الحاسبة) باستخدام العملية المنطقية ونكتب = 01110000 في المتغير ، أي اكتب أهم القطع فقط. يتم نقل البتات ذات الترتيب المنخفض إلى اليسار بأربعة أحرف في البداية ، ثم تتم كتابتها في المتغير lo = 00110000. بعد ذلك ، نقوم بتشكيل صفيف من 4 بايت يحتوي على معلومات حول الحرف الذي سيتم عرضه. الآن نقوم بتعيين بتات التكوين (0-3 بت) للبايتات الحالية. بعد ذلك ، نرسل بايت العنوان و 4 بايت من المعلومات إلى الشاشة باستخدام وظيفة HAL_I2C_Master_Transmit () ؛

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

  I2C_send(0b00110000,0);   // 8  
  I2C_send(0b00000010,0);   //     
  I2C_send(0b00001100,0);   //   ,  
  I2C_send(0b00000001,0);   //  

نضع هذه الأوامر قبل بداية حلقة لا نهائية ، بحيث يتم إرسال الإعدادات مرة واحدة قبل بدء العمل (مثل إعداد الفراغ في arduinki). تتطلب وظيفة I2C_send ، بالإضافة إلى البايت ، تحديد ما إذا كان سيتم إرسال إعدادات العرض أو البيانات. إذا كانت الوسيطة الثانية للدالة هي 0 ، فإن الإعدادات ، وإذا كانت 1 ، فإن البيانات.

واللمسة الأخيرة - أنت بحاجة إلى وظيفة ترسل موعدًا نهائيًا حرفًا تلو الآخر. كل شيء بسيط جدًا هنا:

void LCD_SendString(char *str)
{
    // *char    
    //    
	while(*str) 
        {                                   
                //    
		I2C_send((uint8_t)(*str), 1); 
                //     1               
                str++;                                     
        }
}

بعد جمع كل هذه الوظائف معًا ، يمكنك كتابة:

  LCD_SendString("  Hello");
  I2C_send(0b11000000,0);   //  
  LCD_SendString("    Habr");

صورة

حسنًا ، اكتشفنا شاشة 1602 ، الآن 2004. الفرق بينهما ضئيل ، حتى هذا الرمز سيعمل بشكل جيد. كل الفرق يأتي لتنظيم عناوين الخلايا على الشاشة. في كلا الشاشتين ، تحتوي الذاكرة على 80 خلية ، في شاشة 1602 ، تكون أول 16 خلية مسؤولة عن السطر الأول ، والخلايا 40 إلى 56 مسؤولة عن السطر الثاني. لا يتم عرض خلايا الذاكرة المتبقية ، لذلك إذا أرسلت 17 حرفًا إلى الشاشة ، فلن يتم نقل آخر خلية على السطر الثاني ، وسيتم تسجيله في خلية ذاكرة لا تحتوي على إخراج للعرض. بشكل أوضح قليلاً ، يتم تنظيم الذاكرة على النحو التالي:

صورة

لاستخدام تغذية الخط ، استخدمت الأمر I2C_send (0b11000000،0) ؛ ، يذهب فقط إلى 40 خلية. عرض 2004 أكثر إثارة للاهتمام.

الصف الأول هو الخلايا من 1 إلى 20
والصف الثاني هو الخلايا من 40 إلى 60
الصف الثالث - الخلايا من 21 إلى 40
الصف الرابع - الخلايا من 60 إلى 80 ،
أي إذا قمت بإرسال أمر

LCD_SendString("___________________1___________________2___________________3___________________4");

نحصل على ما يلي:

صورة

لتنظيم الانتقالات بين الخطوط ، يجب عليك تحريك المؤشر يدويًا إلى خلية الذاكرة المطلوبة ، أو يمكنك استكمال الوظيفة برمجيًا. لقد استقرت حتى الآن على النسخة اليدوية:

  I2C_send(0b10000000,0);   //   1 
  LCD_SendString("  Hello Habr");
  I2C_send(0b11000000,0);   //   2 
  LCD_SendString(" STM32 + LCD 1602");
  I2C_send(0b10010100,0);   //   3 
  LCD_SendString(" +LCD 2004A");
  I2C_send(0b11010100,0);   //   4 
  LCD_SendString(" library HAL");

النتيجة:

صورة

من المحتمل أن يكون كل هذا مع هذه الشاشات ، روابط مفيدة تمكنت بفضلها من اكتشافها بالكامل:

  1. بدا الرمز كثيرًا هنا
  2. تبدو جداول تكوين العرض هنا
  3. تم النظر في الإجراء هنا

البرنامج وورقة البيانات

PS: لا تنس ضبط سطوع الشاشة مسبقًا.

All Articles