जीवन की गीक-शैली का उत्सव: एआरएम और टीएफटी एलसीडी के साथ

परिचय


परिणाम डैलिनज , गुलिक और होशी के पदों को देखने के बाद , मुझे एक बार फिर लगा कि हबर एक केक है।

लिनक्स के लिए HD44780 पर आधारित चरित्र प्रदर्शन ड्राइवर लिखने वाला पहला पोस्ट ( dlinyj से अपने खुद के लिनक्स ड्राइवर बनाना ); इसके उत्कृष्ट उत्तर अच्छे हबीबर्स के पद थे ( गीक की शैली में बधाई, बिना जलाऊ लकड़ी के लेखन ) और होशी ( नए साल की रसभरी - हम रास्पबेरी पाई को एचडी 44780 स्क्रीन को उपवास करते हैं )।

मैं भी जीवन के इस उत्सव में भाग लेना चाहता था और अपने हार्डवेयर vt52-like टर्मिनल को लागू करना चाहता था। मेरे पास एक प्रतीकात्मक प्रदर्शन नहीं था, लेकिन एआरएम कोर्टेक्स-एम 3 पर आधारित एक चीनी देव-बोर्ड था, जिसमें पूर्ण 240x320 टीएफटी डिस्प्ले, आंशिक दस्तावेज थे।

उत्साह का भंडार था, इसलिए, रविवार दोपहर (~ 17 एमएसके) को जगाते हुए, मैंने इस एलसीडी के लिए एक एम्बेडेड ड्राइवर लिखना शुरू कर दिया।

यदि आप एम्बेडेड एआरएम प्रोग्रामिंग, इलेक्ट्रॉनिक्स, या सिर्फ परिणाम में रुचि रखते हैं - कृपया, बिल्ली के नीचे।

लोहा


मेरे निपटान में एक एसटी STM32F103RB माइक्रोकंट्रोलर पर आधारित USB-UART प्रोलिफिक PL-2303HX हार्डवेयर ब्रिज के साथ एक छोटा सा बाह्य उपकरणों का एक गुच्छा और एक अज्ञात कनेक्शन आरेख के साथ Ilitek ILI9320 कंट्रोलर के साथ TFT TFT के साथ एक साधारण डिबग बोर्ड था

इन-सर्किट डिबगर और प्रोग्रामर के रूप में, ओलीमेक्स जेटीएजी एआरएम-टिनि-यूएसबी-एच का उपयोग किया गया था । एक अच्छा उपकरण, यह OpenOCD के साथ ठीक काम करता है।

devboard
devboard


अधिक सटीक रूप से, शुरू में यह भी ज्ञात नहीं था कि एलसीडी पर किस तरह का नियंत्रक है। सब कुछ जो डिस्प्ले मॉड्यूल से सीखा जा सकता है कि यह 16-बिट बस के माध्यम से जुड़ा हुआ है जिसमें nCS , nWR , nRD , BL_EN और RS सिग्नल हैं।
जिसका उद्देश्य अनुमान लगाना मुश्किल नहीं था:


इंटरनेट के चीनी खंड के विस्तार पर पाए जाने वाले प्रलेखन में से एक में, इसके साथ एक समान शुल्क था
इलीटेक 932x मॉड्यूल।

सॉफ्टवेयर इंटरफेस


निम्न स्तर का इंटरफ़ेस

चूंकि RuNet में इस एलसीडी नियंत्रक के साथ काम करने के कई विवरण नहीं हैं, मैं शायद निम्न-स्तरीय इंटरफ़ेस का वर्णन करूंगा।

इस नियंत्रक में अनिवार्य रूप से उनमें से 4 हैं: i80- प्रणाली (समानांतर इंटरफ़ेस, एक एचडी -7807 इंटरफ़ेस के समान समानांतर मेमोरी), SPI, VSYNC (सिस्टम + VSYNC , आंतरिक VSYNC के साथ) और RGB ( VSYNC , HSYNC , ENABLE , बाहरी के साथ) DOTCLK टाइमिंग)। मेरे मामले में, i80- प्रणाली और, संभवतः, SPI (परीक्षण नहीं) उपलब्ध हैं।

चूंकि मैंने केवल प्रणाली का उपयोग किया है, हम इसके विवरण पर एक नज़र डालेंगे। लेख में बहुत अधिक लोड न करने के लिए - यह स्पॉइलर में होगा।

इलेक्ट्रिकल इंटरफ़ेस ILI9320
विद्युत स्तर पर, डिजिटल तकनीक के साथ काम आमतौर पर समय आरेख द्वारा वर्णित किया जाता है। हमारे मामले में, पांच नियंत्रण संकेत और 16-बिट डेटा बस हैं।

nCS को किसी भी जानकारी को प्रेषित करने से पहले, इंटरफ़ेस को nCS सिग्नल के साथ सक्रिय किया जाना चाहिए, इसे 0 पर सेट करना।

इसके अलावा, जब 0 RS सेट किया जाता है, तो रजिस्टर का पता लिखा जाता है, जिसमें जानकारी दर्ज की जाएगी (वास्तविक रिकॉर्डिंग nWR सिग्नल को सक्रिय करके किया जाता है। RS सिग्नल 1 पर वापस सेट हो जाता है।

उसके बाद, वास्तविक रीड या राइट ऑपरेशन किया जाता है (क्रमशः nRD और nWR का उपयोग करके)।

इन प्रक्रियाओं के आरेख निम्नानुसार हैं:
सेशन
पढ़नाLCD पढ़े op
लिखनेएलसीडी लिखने सेशन


GRAM से लिखते / पढ़ते समय, एक विशेष रजिस्टर 0x22 । इसके अलावा, नियंत्रक ऑटो-इंक्रीमेंट कर सकता है
GRAM पते, जो आपको क्रमिक रूप से इसकी सामग्री को पढ़ने / लिखने की अनुमति देते हैं।

चित्र:
सेशन
GRAM पढ़ाएलसीडी GRAM सेशन पढ़ा
ग्राम लेखनLCD GRAM op लिखिए


ऑपरेशन पूरा करने के बाद, nCS वापस 1 पर सेट किया जाता है।

टाइमिंग आरेखों को खींचने के लिए, मुझे एक अद्भुत वेवड्रोम परियोजना मिली जो एक ब्राउज़र में काम करती है। यहां परीक्षण (ऊपर दिए गए चित्र यहां तैयार किए गए थे)।


विद्युत इंटरफ़ेस के आधार पर, निम्न-स्तरीय फ़ंक्शन लिखे गए थे:

lcd_ll_funcs
 void _lcd_select(void) { GPIO_ResetBits(GPIOC, GPIO_Pin_9); } void _lcd_deselect(void) { GPIO_SetBits(GPIOC, GPIO_Pin_9); } void _lcd_rs_set(void) { GPIO_SetBits(GPIOC, GPIO_Pin_8); } void _lcd_rs_reset(void) { GPIO_ResetBits(GPIOC, GPIO_Pin_8); } void _lcd_rd_en(void) { GPIO_ResetBits(GPIOC, GPIO_Pin_11); } void _lcd_rd_dis(void) { GPIO_SetBits(GPIOC, GPIO_Pin_11); } void _lcd_wr_en(void) { GPIO_ResetBits(GPIOC, GPIO_Pin_10); } void _lcd_wr_dis(void) { GPIO_SetBits(GPIOC, GPIO_Pin_10); } void _lcd_bl_en(void) { GPIO_SetBits(GPIOC, GPIO_Pin_12); } void _lcd_bl_dis(void) { GPIO_ResetBits(GPIOC, GPIO_Pin_12); } // changes DB[15:0] GPIO pins mode void lcd_gpio_conf(GPIOMode_TypeDef mode); void _lcd_put_data(u16 data) { // data[0-7] -> GPIOC[0-7], data[8-15] -> GPIOB[8-15] GPIOB->ODR = (GPIOB->ODR&0x00ff)|(data&0xff00); GPIOC->ODR = (GPIOC->ODR&0xff00)|(data&0x00ff); } u16 _lcd_read_data(void) { lcd_gpio_conf(GPIO_Mode_IN_FLOATING); u16 result = (GPIOB->IDR&0xff00)|(GPIOC->IDR&0x00ff); lcd_gpio_conf(GPIO_Mode_Out_PP); return result; } // assume that lcd_select() was done before it void _lcd_tx_reg(u8 addr) { _lcd_put_data(addr); _lcd_rs_reset(); _lcd_wr_en(); _lcd_wr_dis(); _lcd_rs_set(); } // assume that _lcd_tx_reg(u8) was done before it void _lcd_tx_data(u16 data) { _lcd_put_data(data); _lcd_wr_en(); _lcd_wr_dis(); } // assume that _lcd_tx_reg(u8) was done before it u16 _lcd_rx_data(void) { _lcd_rd_en(); u16 result = _lcd_read_data(); _lcd_rd_dis(); return result; } 


तेजी लाने के लिए, आप इन कार्यों को इनलाइन कर सकते हैं और उन्हें मैक्रोज़ में बदल सकते हैं (जिसके साथ ग्रहण बहुत अनुकूल नहीं है, दुर्भाग्य से)।

इन कार्यों के आधार पर, रजिस्टर में लिखने के लिए, रजिस्टर से पढ़ने के लिए, और इमेज ब्लिटिंग के लिए कार्य कार्यान्वित किए जाते हैं।

उच्च स्तरीय इंटरफ़ेस

कार्यक्रम के मुख्य भाग के लिए एलसीडी फ़ंक्शन निम्नलिखित एपीआई के माध्यम से उपलब्ध हैं:

 u16 lcd_init(void); void lcd_set_cursor(u16 x, u16 y); void lcd_set_window(u16 left, u16 top, u16 right, u16 bottom); void lcd_fill(u32 color); void lcd_rect(u16 left, u16 top, u16 right, u16 bottom); void lcd_put_char_at(u32 data, u16 x, u16 y); u32 lcd_get_fg(void); u32 lcd_get_bg(void); void lcd_set_fg(u32 color); void lcd_set_bg(u32 color); 


टर्मिनल फ़ंक्शंस इस इंटरफ़ेस का उपयोग उनके सभी कार्यों के लिए करते हैं।

सबसे दिलचस्प हिस्सा एक प्रतीक को खींचने का कार्य है, क्योंकि फोंट के साथ सभी काम इसके पीछे छिपे हुए हैं। यह इस तरह दिखता है:

lcd_put_char_at
 void lcd_put_char_at(u32 data, u16 x, u16 y) { u8 xsize, ysize; u8 *char_img; lcd_get_char(data, &xsize, &ysize, &char_img); lcd_set_cursor(x, y); lcd_set_window(x, y, x + xsize, y + ysize); _lcd_select(); _lcd_tx_reg(0x22); // works only for 8xN fonts for(u8 i = 0; i < ysize; i++) { u8 str = char_img[i]; for(u8 j = 0; j < xsize; j++) { _lcd_tx_data((str&(1<<(xsize-j-1)))?fg_color:bg_color); } } _lcd_deselect(); } 


जैसा कि आप देख सकते हैं, प्रतीक बिटमैप और उसके आकार का लिंक प्रतीक कोड द्वारा lcd_get_char फ़ंक्शन से आता है (यह 32-बिट है ताकि अतिरिक्त वर्णों के साथ ASCII भाग को स्पर्श न करें)।

वर्तमान में, एक फ़ॉन्ट का उपयोग किया जाता है जिसमें ASCII तालिका के नीचे, प्लस "हेरिंगबोन" होता है। जो लोग इसे खोजने की कोशिश कर सकते हैं)

डिबग
डिबग


सबसे कम दिलचस्प और सबसे महंगा (लेखन समय के संदर्भ में) प्रदर्शन आरंभीकरण समारोह है:
lcd_init: उन लोगों के लिए जो डरना चाहते हैं
 u16 lcd_init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio_conf; gpio_conf.GPIO_Speed = GPIO_Speed_50MHz; gpio_conf.GPIO_Mode = GPIO_Mode_Out_PP; gpio_conf.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_Init(GPIOC, &gpio_conf); lcd_gpio_conf(GPIO_Mode_Out_PP); // to init state (0xffff on db0-15, backlit is disabled, nCS, nWR, nRD and RS are high) _lcd_bl_dis(); _lcd_put_data(0xffff); _lcd_deselect(); _lcd_wr_dis(); _lcd_rd_dis(); _lcd_rs_set(); // osc enable _lcd_bl_dis(); lcd_write_reg(0x00, 0x0001); delay_ms(100); u16 lcd_code = lcd_read_reg(0x00); delay_ms(100); // driver output control (S720-S1) lcd_write_reg(0x01, 0x0100); // driving wave control (line inv) lcd_write_reg(0x02, 0x0700); // entry mode (horiz, dir(h+,v+), hwm-, bgr+) lcd_write_reg(0x03, 0x1030); // resize (off) lcd_write_reg(0x04, 0x0000); // display control 2 (skip 2 lines on front porch and on back porch) lcd_write_reg(0x08, 0x0202); // display control 3-4 (scan mode normal, fmark off) lcd_write_reg(0x09, 0x0000); lcd_write_reg(0x0a, 0x0000); // RGB disp iface control (int clock, sys int, 16bit) lcd_write_reg(0x0c, 0x0001); // frame marker position (isn't used) lcd_write_reg(0x0d, 0x0000); // RGB disp iface control 2 (all def, we don't use rgb) lcd_write_reg(0x0f, 0x0000); // power on seq lcd_write_reg(0x07, 0x0021); delay_ms(10); // turn on power supply and configure it (enable sources, set contrast, power supply on) lcd_write_reg(0x10, 0x16b0); // set normal voltage and max dcdc freq lcd_write_reg(0x11, 0x0007); // internal vcomh (see 0x29), pon, gray level (0x08) lcd_write_reg(0x12, 0x0118); // set vcom to 0.92 * vreg1out lcd_write_reg(0x13, 0x0b00); // vcomh = 0.69 * vreg1out lcd_write_reg(0x29, 0x0000); // set x and y range lcd_write_reg(0x50, 0); lcd_write_reg(0x51, LCD_WIDTH-1); lcd_write_reg(0x52, 0); lcd_write_reg(0x53, LCD_HEIGHT-1); // gate scan control (scan direction, display size) lcd_write_reg(0x60, 0x2700); lcd_write_reg(0x61, 0x0001); lcd_write_reg(0x6a, 0x0000); // partial displays off for(u8 addr = 0x80; addr < 0x86; addr++) { lcd_write_reg(addr, 0x0000); } // panel iface control (19 clock/line) lcd_write_reg(0x90, 0x0013); // lcd timings lcd_write_reg(0x92, 0x0000); lcd_write_reg(0x93, 0x0001); lcd_write_reg(0x95, 0x0110); lcd_write_reg(0x97, 0x0000); lcd_write_reg(0x98, 0x0000); lcd_write_reg(0x07, 0x0133); // turn on backlit after init done _lcd_bl_en(); return lcd_code; } 


टर्मिनल कार्यान्वयन


यह हिस्सा अचूक है। पिछले लेखों में से कुछ कोड के साथ एक अनफिल्ड टर्मिनल लागू किया गया है।

क्रम से बचो
पलायन क्रम:
  • \ 033 [ए = कर्सर को एक पंक्ति ऊपर ले जाएं
  • \ 033 [बी = कर्सर को एक पंक्ति नीचे ले जाएं
  • \ 033 [C = कर्सर को दाईं ओर ले जाएं
  • \ 033 [डी = कर्सर को बाईं ओर एक स्थिति में ले जाएँ
  • \ 033 [H = कर्सर को ऊपरी बाएँ कोने में ले जाएँ - घर (स्थिति 0,0)
  • \ 033 [जे = सब कुछ साफ़ करें, कर्सर घर वापस न करें!
  • \ 033 [K = पंक्ति के अंत में मिटाता है, कर्सर को घर नहीं लौटाता है!
  • \ 033 [एम = नया चरित्र मानचित्र - कार्यान्वित नहीं किया गया
  • \ 033 [Y = स्थिति, YX लेता है
  • \ 033 [X = स्थिति, XY लेता है
  • \ 033 [R = CGRAM मेमोरी सेल चयन - लागू नहीं, क्योंकि कोई CGRAM नहीं
  • \ 033 [वी = स्क्रॉलिंग सक्षम - कार्यान्वित नहीं
  • \ 033 [डब्ल्यू = स्क्रॉलिंग अक्षम - लागू नहीं
  • \ 033 [बी = बैकलाइट चालू / बंद - लागू नहीं


अन्य उपयोगी कोड:
  • \ r = कैरिज रिटर्न (वर्तमान लाइन पर कर्सर को स्थिति 0 पर लौटाएं!)
  • \ n = नई पंक्ति
  • \ t = टैब (डिफ़ॉल्ट 3 वर्ण)



संचार


बाहरी दुनिया के साथ बातचीत करने के लिए, USART1 उपयोग यूएसबी PL-2303HX UART PL-2303HX माध्यम से अतुल्यकालिक मोड में किया जाता है।

लिनक्स होस्ट के नजरिए से, यह /dev/ttyUSBx । दुर्भाग्य से, pl2303 लिए ड्राइवर बल्कि अस्थिर थे। लेकिन, जैसे ही वे उठाते हैं, वे शालीनता से काम करते हैं।

मुख्य लूप (जो खाली है) में यूएआरटी को प्रदूषित नहीं करने के लिए, इसके साथ काम इंटरप्ट पर लागू किया जाता है।

सॉफ़्टवेयर के दृष्टिकोण से, इसका मतलब है कि USART1 को प्रारंभ करने के बाद, आपको NVIC में संबंधित इंटरप्ट वेक्टर को कॉन्फ़िगर करना होगा।

यह इस तरह दिखता है:
 NVIC_InitTypeDef nvic_conf; nvic_conf.NVIC_IRQChannel = USART1_IRQn; nvic_conf.NVIC_IRQChannelPreemptionPriority = 0; nvic_conf.NVIC_IRQChannelSubPriority = 2; nvic_conf.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_conf); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); 


अंतिम कमांड रिसेप्शन रजिस्टर USART1 में भरने की घटना को हल करता है।

तदनुसार, प्रसंस्करण इस तरह दिखता है:
 void USART1_IRQHandler(void) { u8 data = USART1->DR; uart_write_byte(data); handle_byte(data); } 


बाइट बैक (ईको) भेजें और हैंडलर को कॉल करें, जो एक साधारण स्टेट मशीन है।
handle_byte (u8)
 // escape sequence handling vars u8 escape_seq = 0; u8 buf[10]; void handle_byte(u8 data) { if((!escape_seq) && (data == 0x1b)) { escape_seq = 1; } else if (escape_seq == 1) { buf[escape_seq] = data; escape_seq++; if(data != '[') { escape_seq = 0; } } else if (escape_seq == 2) { switch(data) { case 'A': lcd_term_set_cursor(lcd_term_row()-1, lcd_term_col()); break; case 'B': lcd_term_set_cursor(lcd_term_row()+1, lcd_term_col()); break; case 'C': lcd_term_set_cursor(lcd_term_row(), lcd_term_col()+1); break; case 'D': lcd_term_set_cursor(lcd_term_row(), lcd_term_col()-1); break; case 'H': lcd_term_set_cursor(0, 0); break; case 'J': lcd_term_clear(); break; case 'K': lcd_term_flush_str(); break; case 'X': case 'Y': buf[escape_seq] = data; escape_seq++; return; } escape_seq = 0; } else if(escape_seq == 3) { buf[escape_seq] = data; escape_seq++; } else if(escape_seq == 4) { u8 row = (buf[2] == 'Y') ? buf[3] - 037 : data - 037; u8 col = (buf[2] == 'Y') ? data - 037 : buf[3] - 037; lcd_term_set_cursor(row, col); escape_seq = 0; } else { lcd_term_put_str(&data, 1); } } 


सभी कोड github रिपॉजिटरी में प्रकाशित हुआ है।

पुनश्च


इस पोस्ट को लिखने में लगभग 6 घंटे लगे। हार्डवेयर और सॉफ्टवेयर भागों को लिखना और डिबग करना - लगभग 13 घंटे।

इसे पढ़ने वाले सभी को धन्यवाद। सभी स्लग और अन्य कीड़ों के बारे में, एक व्यक्तिगत में लिखें।

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


All Articles