Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

1.5" ILI9163 lcd screen working

Tue Oct 15, 2013 10:02 am

First, I don't know if I'm posting this at the right forum, maybe it belongs to Beginners, if that's so, feel free to move it.

My brother gave me about two months ago a pair of keychain photoframes (the brand is NPlay, but it can be found under other brands), that he had bought for 2€ each as a bargain in Worten, along with the datasheet for the ILI9163 that he suspected could drive the 1,5" screen, and a URL:http://www.waitingforfriday.com/index.p ... Photoframe . Here the author, Simon Inns, explains how he deduced the pinout and built an Atmel C library to drive the screen. Well, I don't know much C and I'm trying to learn Python right now. I thought that porting it to Python for the Pi would be a fun way to learn, so I desoldered the LCD (breaking one of the two in the process) and made an adaptor PCB following Simon's Eagle CAD project. The syntax is not very dissimilar, but there were two minor problems: first, Simon used in the C code direct (parallel) writing to the GPIO registers (the LCD controller is hardwired for parallel input),something that it's not available for Pi Python, and that forced me to use loops to write to the controller with GPIO.output, 1 bit at a time. And second, he used 8 bit (short) integers, so I had to use some creative bit operations to do 2-byte writes with Python integers. I could have used char variables,but I was translating the code directly. To my surprise, my code works, I can print text, dots, lines, rectangles, filled rectangles and circles (all Simon's work, I just ported it), but very slowly. The clear_display function takes about 4 seconds, filling the display with text about the same. The bat and ball animation in the last part of the program runs at reasonable speed, but just running it the CPU load climbs to 100%.

I would like to use the display at the front of my Pi-formerly-old-DVB-T-case to select menu options with some switches or a digital joystick (for example, I would like to run a headless raspbian with a MIDI synthesizer, maybe fluidsynth, and tweak it to select the GM instrument and volume, so I could carry my USB MIDI controller and the Pi and practice anywhere... before that I have to learn lots more), but I think it's a little slow for that right now.

The question is: is there some way to speed up the code? I'm sure that someone with more Python knowledge than me (nearly everyone) can give me some clues. The easy answer is overclocking, but my Pi is already running at 800 MHz and I don't really want to overclock it more, anyway the speed gain would be small. Keep in mind that I'm beggining to learn Python, so I assume that my code is..... let's say improvable, as my english is.

Of course, I would be glad if this is useful to somebody. I think that this neat 128x128 pixels 262K colour LCD can be useful in some projects, and it's good value for the price. I have seen them (or a very similar product) on Ebay from 4$, and on Alibaba there is loads of them

Here is the code :

Code: Select all

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#************************************************************************
#   
#   Based on ILI9163 128x128 LCD library
#    Copyright (C) 2012 Simon Inns
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#	Email: [email protected]
#
#************************************************************************/

from time import sleep
import RPi.GPIO  as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)

# array 6x8, Unicode set
font5x8=(
[0x00,0x00,0x00,0x00,0x00,0x00],   #   0x00 0
[0x00,0x64,0x18,0x04,0x64,0x18],   #   0x01 1
[0x00,0x3c,0x40,0x40,0x20,0x7c],   #   0x02 2
[0x00,0x0c,0x30,0x40,0x30,0x0c],   #   0x03 3
[0x00,0x3c,0x40,0x30,0x40,0x3c],   #   0x04 4
[0x00,0x00,0x3e,0x1c,0x08,0x00],   #   0x05 5
[0x00,0x04,0x1e,0x1f,0x1e,0x04],   #   0x06 6
[0x00,0x10,0x3c,0x7c,0x3c,0x10],   #   0x07 7
[0x00,0x20,0x40,0x3e,0x01,0x02],   #   0x08 8
[0x00,0x22,0x14,0x08,0x14,0x22],   #   0x09 9
[0x00,0x00,0x38,0x28,0x38,0x00],   #   0x0a 10
[0x00,0x00,0x10,0x38,0x10,0x00],   #  0x0b 11
[0x00,0x00,0x00,0x10,0x00,0x00],   #  0x0c 12
[0x00,0x08,0x78,0x08,0x00,0x00],   #   0x0d 13
[0x00,0x00,0x15,0x15,0x0a,0x00],   #   0x0e 14
[0x00,0x7f,0x7f,0x09,0x09,0x01],   #   0x0f 15
[0x00,0x10,0x20,0x7f,0x01,0x01],   #   0x10 16
[0x00,0x04,0x04,0x00,0x01,0x1f],   #   0x11 17
[0x00,0x00,0x19,0x15,0x12,0x00],   #   0x12 18
[0x00,0x40,0x60,0x50,0x48,0x44],   #   0x13 19
[0x00,0x06,0x09,0x09,0x06,0x00],   #   0x14 20
[0x00,0x0f,0x02,0x01,0x01,0x00],   #   0x15 21
[0x00,0x00,0x01,0x1f,0x01,0x00],   #   0x16 22
[0x00,0x44,0x44,0x4a,0x4a,0x51],   #   0x17 23
[0x00,0x14,0x74,0x1c,0x17,0x14],   #   0x18 24
[0x00,0x51,0x4a,0x4a,0x44,0x44],   #   0x19 25
[0x00,0x00,0x00,0x04,0x04,0x04],   #   0x1a 26
[0x00,0x00,0x7c,0x54,0x54,0x44],   #   0x1b 27
[0x00,0x08,0x08,0x2a,0x1c,0x08],   #   0x1c 28
[0x00,0x7c,0x00,0x7c,0x44,0x7c],   #   0x1d 29
[0x00,0x04,0x02,0x7f,0x02,0x04],   #   0x1e 30
[0x00,0x10,0x20,0x7f,0x20,0x10],   #   0x1f 31
[0x00,0x00,0x00,0x00,0x00,0x00],   #   0x20 32
[0x00,0x00,0x00,0x6f,0x00,0x00],   # ! 0x21 33
[0x00,0x00,0x07,0x00,0x07,0x00],   # " 0x22 34
[0x00,0x14,0x7f,0x14,0x7f,0x14],   # # 0x23 35
[0x00,0x00,0x07,0x04,0x1e,0x00],   # $ 0x24 36
[0x00,0x23,0x13,0x08,0x64,0x62],   # % 0x25 37
[0x00,0x36,0x49,0x56,0x20,0x50],   # & 0x26 38
[0x00,0x00,0x00,0x07,0x00,0x00],   # ' 0x27 39
[0x00,0x00,0x1c,0x22,0x41,0x00],   # ( 0x28 40
[0x00,0x00,0x41,0x22,0x1c,0x00],   # ) 0x29 41
[0x00,0x14,0x08,0x3e,0x08,0x14],   # * 0x2a 42
[0x00,0x08,0x08,0x3e,0x08,0x08],   # + 0x2b 43
[0x00,0x00,0x50,0x30,0x00,0x00],   # , 0x2c 44
[0x00,0x08,0x08,0x08,0x08,0x08],   # - 0x2d 45
[0x00,0x00,0x60,0x60,0x00,0x00],   # . 0x2e 46
[0x00,0x20,0x10,0x08,0x04,0x02],   # / 0x2f 47
[0x00,0x3e,0x51,0x49,0x45,0x3e],   # 0 0x30 48
[0x00,0x00,0x42,0x7f,0x40,0x00],   # 1 0x31 49
[0x00,0x42,0x61,0x51,0x49,0x46],   # 2 0x32 50
[0x00,0x21,0x41,0x45,0x4b,0x31],   # 3 0x33 51
[0x00,0x18,0x14,0x12,0x7f,0x10],   # 4 0x34 52
[0x00,0x27,0x45,0x45,0x45,0x39],   # 5 0x35 53
[0x00,0x3c,0x4a,0x49,0x49,0x30],   # 6 0x36 54
[0x00,0x01,0x71,0x09,0x05,0x03],   # 7 0x37 55
[0x00,0x36,0x49,0x49,0x49,0x36],   # 8 0x38 56
[0x00,0x06,0x49,0x49,0x29,0x1e],   # 9 0x39 57
[0x00,0x00,0x36,0x36,0x00,0x00],   # : 0x3a 58
[0x00,0x00,0x56,0x36,0x00,0x00],   # ; 0x3b 59
[0x00,0x08,0x14,0x22,0x41,0x00],   # < 0x3c 60
[0x00,0x14,0x14,0x14,0x14,0x14],   # = 0x3d 61
[0x00,0x00,0x41,0x22,0x14,0x08],   # > 0x3e 62
[0x00,0x02,0x01,0x51,0x09,0x06],   # ? 0x3f 63
[0x00,0x3e,0x41,0x5d,0x49,0x4e],   # @ 0x40 64
[0x00,0x7e,0x09,0x09,0x09,0x7e],   # A 0x41 65
[0x00,0x7f,0x49,0x49,0x49,0x36],   # B 0x42 66
[0x00,0x3e,0x41,0x41,0x41,0x22],   # C 0x43 67
[0x00,0x7f,0x41,0x41,0x41,0x3e],   # D 0x44 68
[0x00,0x7f,0x49,0x49,0x49,0x41],   # E 0x45 69
[0x00,0x7f,0x09,0x09,0x09,0x01],   # F 0x46 70
[0x00,0x3e,0x41,0x49,0x49,0x7a],   # G 0x47 71
[0x00,0x7f,0x08,0x08,0x08,0x7f],   # H 0x48 72
[0x00,0x00,0x41,0x7f,0x41,0x00],   # I 0x49 73
[0x00,0x20,0x40,0x41,0x3f,0x01],   # J 0x4a 74
[0x00,0x7f,0x08,0x14,0x22,0x41],   # K 0x4b 75
[0x00,0x7f,0x40,0x40,0x40,0x40],   # L 0x4c 76
[0x00,0x7f,0x02,0x0c,0x02,0x7f],   # M 0x4d 77
[0x00,0x7f,0x04,0x08,0x10,0x7f],   # N 0x4e 78
[0x00,0x3e,0x41,0x41,0x41,0x3e],   # O 0x4f 79
[0x00,0x7f,0x09,0x09,0x09,0x06],   # P 0x50 80
[0x00,0x3e,0x41,0x51,0x21,0x5e],   # Q 0x51 81
[0x00,0x7f,0x09,0x19,0x29,0x46],   # R 0x52 82
[0x00,0x46,0x49,0x49,0x49,0x31],   # S 0x53 83
[0x00,0x01,0x01,0x7f,0x01,0x01],   # T 0x54 84
[0x00,0x3f,0x40,0x40,0x40,0x3f],   # U 0x55 85
[0x00,0x0f,0x30,0x40,0x30,0x0f],   # V 0x56 86
[0x00,0x3f,0x40,0x30,0x40,0x3f],   # W 0x57 87
[0x00,0x63,0x14,0x08,0x14,0x63],   # X 0x58 88
[0x00,0x07,0x08,0x70,0x08,0x07],   # Y 0x59 89
[0x00,0x61,0x51,0x49,0x45,0x43],   # Z 0x5a 90
[0x00,0x3c,0x4a,0x49,0x29,0x1e],   # [ 0x5b 91
[0x00,0x02,0x04,0x08,0x10,0x20],   # \ 0x5c 92
[0x00,0x00,0x41,0x7f,0x00,0x00],   # ] 0x5d 93
[0x00,0x04,0x02,0x01,0x02,0x04],   # ^ 0x5e 94
[0x00,0x40,0x40,0x40,0x40,0x40],   # _ 0x5f 95
[0x00,0x00,0x00,0x03,0x04,0x00],   # ` 0x60 96
[0x00,0x20,0x54,0x54,0x54,0x78],   # a 0x61 97
[0x00,0x7f,0x48,0x44,0x44,0x38],   # b 0x62 98
[0x00,0x38,0x44,0x44,0x44,0x20],   # c 0x63 99
[0x00,0x38,0x44,0x44,0x48,0x7f],   # d 0x64 100
[0x00,0x38,0x54,0x54,0x54,0x18],   # e 0x65 101
[0x00,0x08,0x7e,0x09,0x01,0x02],   # f 0x66 102
[0x00,0x0c,0x52,0x52,0x52,0x3e],   # g 0x67 103
[0x00,0x7f,0x08,0x04,0x04,0x78],   # h 0x68 104
[0x00,0x00,0x44,0x7d,0x40,0x00],   # i 0x69 105
[0x00,0x20,0x40,0x44,0x3d,0x00],   # j 0x6a 106
[0x00,0x00,0x7f,0x10,0x28,0x44],   # k 0x6b 107
[0x00,0x00,0x41,0x7f,0x40,0x00],   # l 0x6c 108
[0x00,0x7c,0x04,0x18,0x04,0x78],   # m 0x6d 109
[0x00,0x7c,0x08,0x04,0x04,0x78],   # n 0x6e 110
[0x00,0x38,0x44,0x44,0x44,0x38],   # o 0x6f 111
[0x00,0x7c,0x14,0x14,0x14,0x08],   # p 0x70 112
[0x00,0x08,0x14,0x14,0x18,0x7c],   # q 0x71 113
[0x00,0x7c,0x08,0x04,0x04,0x08],   # r 0x72 114
[0x00,0x48,0x54,0x54,0x54,0x20],   # s 0x73 115
[0x00,0x04,0x3f,0x44,0x40,0x20],   # t 0x74 116
[0x00,0x3c,0x40,0x40,0x20,0x7c],   # u 0x75 117
[0x00,0x1c,0x20,0x40,0x20,0x1c],   # v 0x76 118
[0x00,0x3c,0x40,0x30,0x40,0x3c],   # w 0x77 119
[0x00,0x44,0x28,0x10,0x28,0x44],   # x 0x78 120
[0x00,0x0c,0x50,0x50,0x50,0x3c],   # y 0x79 121
[0x00,0x44,0x64,0x54,0x4c,0x44],   # z 0x7a 122
[0x00,0x00,0x08,0x36,0x41,0x41],   # [ 0x7b 123
[0x00,0x00,0x00,0x7f,0x00,0x00],   # | 0x7c 124
[0x00,0x41,0x41,0x36,0x08,0x00],   # ] 0x7d 125
[0x00,0x04,0x02,0x04,0x08,0x04],   # ~ 0x7e 126
[0x00,0x7f,0x6b,0x6b,0x6b,0x7f],   #   0x7f 127
[0x00,0x00,0x7c,0x44,0x7c,0x00],   # ¬ 0x80 128
[0x00,0x00,0x08,0x7c,0x00,0x00],   # ? 0x81 129
[0x00,0x00,0x64,0x54,0x48,0x00],   # ‚ 0x82 130
[0x00,0x00,0x44,0x54,0x28,0x00],   # ƒ 0x83 131
[0x00,0x00,0x1c,0x10,0x78,0x00],   # „ 0x84 132
[0x00,0x00,0x5c,0x54,0x24,0x00],   # … 0x85 133
[0x00,0x00,0x78,0x54,0x74,0x00],   # † 0x86 134
[0x00,0x00,0x64,0x14,0x0c,0x00],   # ‡ 0x87 135
[0x00,0x00,0x7c,0x54,0x7c,0x00],   # ˆ 0x88 136
[0x00,0x00,0x5c,0x54,0x3c,0x00],   # ‰ 0x89 137
[0x00,0x78,0x24,0x26,0x25,0x78],   # Š 0x8a 138
[0x00,0x78,0x25,0x26,0x24,0x78],   # ‹ 0x8b 139
[0x00,0x70,0x2a,0x29,0x2a,0x70],   # Π0x8c 140
[0x00,0x78,0x25,0x24,0x25,0x78],   # ? 0x8d 141
[0x00,0x20,0x54,0x56,0x55,0x78],   # ] 0x8e 142
[0x00,0x20,0x55,0x56,0x54,0x78],   # ? 0x8f 143
[0x00,0x20,0x56,0x55,0x56,0x78],   # ? 0x90 144
[0x00,0x20,0x55,0x54,0x55,0x78],   # ‘ 0x91 145
[0x00,0x7c,0x54,0x56,0x55,0x44],   # ’ 0x92 146
[0x00,0x7c,0x55,0x56,0x54,0x44],   # “ 0x93 147
[0x00,0x7c,0x56,0x55,0x56,0x44],   # ” 0x94 148
[0x00,0x7c,0x55,0x54,0x55,0x44],   # • 0x95 149
[0x00,0x38,0x54,0x56,0x55,0x18],   # – 0x96 150
[0x00,0x38,0x55,0x56,0x54,0x18],   # — 0x97 151
[0x00,0x38,0x56,0x55,0x56,0x18],   # ˜ 0x98 152
[0x00,0x38,0x55,0x54,0x55,0x18],   # ™ 0x99 153
[0x00,0x00,0x44,0x7e,0x45,0x00],   # š 0x9a 154
[0x00,0x00,0x45,0x7e,0x44,0x00],   # › 0x9b 155
[0x00,0x00,0x46,0x7d,0x46,0x00],   # œ 0x9c 156
[0x00,0x00,0x45,0x7c,0x45,0x00],   # ? 0x9d 157
[0x00,0x00,0x48,0x7a,0x41,0x00],   # ~ 0x9e 158
[0x00,0x00,0x49,0x7a,0x40,0x00],   # Ÿ 0x9f 159
[0x00,0x00,0x4a,0x79,0x42,0x00],   #   0xa0 160
[0x00,0x00,0x49,0x78,0x41,0x00],   # ¡ 0xa1 161
[0x00,0x38,0x44,0x46,0x45,0x38],   # ¢ 0xa2 162
[0x00,0x38,0x45,0x46,0x44,0x38],   # £ 0xa3 163
[0x00,0x38,0x46,0x45,0x46,0x38],   # ¤ 0xa4 164
[0x00,0x38,0x45,0x44,0x45,0x38],   # ¥ 0xa5 165
[0x00,0x30,0x48,0x4a,0x49,0x30],   # ¦ 0xa6 166
[0x00,0x30,0x49,0x4a,0x48,0x30],   # § 0xa7 167
[0x00,0x30,0x4a,0x49,0x4a,0x30],   # ¨ 0xa8 168
[0x00,0x30,0x49,0x48,0x49,0x30],   # © 0xa9 169
[0x00,0x3c,0x40,0x42,0x41,0x3c],   # ª 0xaa 170
[0x00,0x3c,0x41,0x42,0x40,0x3c],   # « 0xab 171
[0x00,0x3c,0x42,0x41,0x42,0x3c],   # ¬ 0xac 172
[0x00,0x3c,0x41,0x40,0x41,0x3c],   # ­  0xad 173
[0x00,0x3c,0x40,0x42,0x21,0x7c],   # ® 0xae 174
[0x00,0x3c,0x41,0x42,0x20,0x7c],   # ¯ 0xaf 175
[0x00,0x38,0x42,0x41,0x22,0x78],   # ° 0xb0 176
[0x00,0x3c,0x41,0x40,0x21,0x7c],   # ± 0xb1 177
[0x00,0x4e,0x51,0x71,0x11,0x0a],   # ² 0xb2 178
[0x00,0x58,0x64,0x64,0x24,0x10],   # ³ 0xb3 179
[0x00,0x7c,0x0a,0x11,0x22,0x7d],   # ´ 0xb4 180
[0x00,0x78,0x12,0x09,0x0a,0x71],   # µ 0xb5 181
[0x00,0x00,0x00,0x04,0x02,0x01],   # ¶ 0xb6 182
[0x00,0x01,0x02,0x04,0x00,0x00],   # · 0xb7 183
[0x00,0x00,0x02,0x00,0x02,0x00],   # ¸ 0xb8 184
[0x00,0x30,0x48,0x45,0x40,0x20],   # ¹ 0xb9 185
[0x00,0x00,0x00,0x7b,0x00,0x00],   # º 0xba 186
[0x00,0x38,0x44,0x44,0x38,0x44],   # » 0xbb 187
[0x00,0x40,0x3e,0x49,0x49,0x36],   # ¼ 0xbc 188
[0x00,0x08,0x04,0x08,0x70,0x0c],   # ½ 0xbd 189
[0x00,0x60,0x50,0x48,0x50,0x60],   # ¾ 0xbe 190
[0x00,0x20,0x52,0x55,0x59,0x30],   # ¿ 0xbf 191
[0x00,0x38,0x54,0x54,0x54,0x00],   # À 0xc0 192
[0x00,0x00,0x00,0x7f,0x41,0x00],   # Á 0xc1 193
[0x00,0x40,0x22,0x14,0x18,0x60],   # Â 0xc2 194
[0x00,0x7c,0x20,0x20,0x1c,0x20],   # Ã 0xc3 195
[0x00,0x44,0x3c,0x04,0x7c,0x44],   # Ä 0xc4 196
[0x00,0x40,0x3c,0x12,0x12,0x0c],   # Å 0xc5 197
[0x00,0x41,0x63,0x55,0x49,0x41],   # Æ 0xc6 198
[0x00,0x38,0x44,0x44,0x3c,0x04],   # Ç 0xc7 199
[0x00,0x08,0x04,0x3c,0x44,0x24],   # È 0xc8 200
[0x00,0x08,0x14,0x7f,0x14,0x08],   # É 0xc9 201
[0x00,0x4e,0x71,0x01,0x71,0x4e],   # Ê 0xca 202
[0x00,0x45,0x29,0x11,0x29,0x45],   # Ë 0xcb 203
[0x00,0x0d,0x51,0x51,0x51,0x3d],   # Ì 0xcc 204
[0x00,0x00,0x00,0x05,0x02,0x05],   # Í 0xcd 205
[0x00,0x40,0x00,0x40,0x00,0x40],   # Î 0xce 206
[0x00,0x00,0x08,0x1c,0x3e,0x00],   # Ï 0xcf 207
[0x00,0x1c,0x1c,0x1c,0x00,0x00],   # Ð 0xd0 208
[0x00,0x00,0x70,0x08,0x07,0x00],   # Ñ 0xd1 209
[0x00,0x00,0x08,0x08,0x08,0x00],   # Ò 0xd2 210
[0x00,0x00,0x1d,0x15,0x17,0x00],   # Ó 0xd3 211
[0x00,0x00,0x07,0x05,0x07,0x00],   # Ô 0xd4 212
[0x00,0x00,0x11,0x15,0x0a,0x00],   # Õ 0xd5 213
[0x00,0x00,0x00,0x00,0x00,0x00],   # Ö 0xd6 214
[0x00,0x04,0x3c,0x41,0x20,0x00],   # × 0xd7 215
[0x00,0x7c,0x16,0x15,0x16,0x08],   # Ø 0xd8 216
[0x00,0x21,0x16,0x08,0x34,0x42],   # Ù 0xd9 217
[0x00,0x7f,0x09,0x1d,0x01,0x03],   # Ú 0xda 218
[0x00,0x38,0x54,0x54,0x14,0x08],   # Û 0xdb 219
[0x00,0x00,0x00,0x7c,0x40,0x40],   # Ü 0xdc 220
[0x00,0x7f,0x0e,0x1c,0x38,0x7f],   # Ý 0xdd 221
[0x00,0x41,0x22,0x5d,0x22,0x1c],   # Þ 0xde 222
[0x00,0x1c,0x3e,0x1c,0x08,0x00],   # ß 0xdf 223
[0x00,0x7f,0x7f,0x7f,0x7f,0x7f],   # à 0xe0 224
[0x00,0x77,0x7b,0x01,0x7b,0x77],   # á 0xe1 225
[0x00,0x7f,0x43,0x75,0x43,0x7f],   # â 0xe2 226
[0x00,0x7f,0x6f,0x55,0x43,0x7f],   # ã 0xe3 227
[0x00,0x40,0x40,0x40,0x40,0x40],   # ä 0xe4 228
[0x00,0x44,0x42,0x5f,0x42,0x44],   # å 0xe5 229
[0x00,0x40,0x5e,0x45,0x5e,0x40],   # æ 0xe6 230
[0x00,0x40,0x48,0x55,0x5e,0x40],   # ç 0xe7 231
[0x00,0x00,0x04,0x08,0x10,0x20],   # è 0xe8 232
[0x00,0x03,0x07,0x0e,0x1c,0x38],   # é 0xe9 233
[0x00,0x01,0x03,0x07,0x0f,0x1f],   # ê 0xea 234
[0x00,0x7c,0x78,0x70,0x60,0x40],   # ë 0xeb 235
[0x00,0x08,0x08,0x1c,0x22,0x1c],   # ì 0xec 236
[0x00,0x00,0x1c,0x22,0x1c,0x00],   # í 0xed 237
[0x00,0x02,0x00,0x08,0x00,0x20],   # î 0xee 238
[0x00,0x04,0x3e,0x3f,0x3e,0x04],   # ï 0xef 239
[0x00,0x10,0x3e,0x7e,0x3e,0x10],   # ð 0xf0 240
[0x00,0x55,0x2a,0x55,0x2a,0x55],   # ñ 0xf1 241
[0x00,0x24,0x2a,0x7f,0x2a,0x12],   # ò 0xf2 242
[0x00,0x04,0x1e,0x1f,0x1e,0x04],   # ó 0xf3 243
[0x00,0x00,0x00,0x00,0x00,0x00],   # ô 0xf4 244
[0x00,0x00,0x00,0x00,0x00,0x00],   # õ 0xf5 245
[0x00,0x00,0x00,0x00,0x00,0x00],   # ö 0xf6 246
[0x00,0x00,0x00,0x00,0x00,0x00],   # ÷ 0xf7 247
[0x00,0x00,0x00,0x00,0x00,0x00],   # ø 0xf8 248
[0x00,0x00,0x00,0x00,0x00,0x00],   # ù 0xf9 249
[0x00,0x00,0x00,0x00,0x00,0x00],   # ú 0xfa 250
[0x00,0x00,0x00,0x00,0x00,0x00],   # û 0xfb 251
[0x00,0x00,0x00,0x00,0x00,0x00],   # ü 0xfc 252
[0x00,0x00,0x00,0x00,0x00,0x00],   # ý 0xfd 253
[0x00,0x00,0x00,0x00,0x00,0x00],   # þ 0xfe 254
[0x00,0x00,0x00,0x00,0x00,0x00])   # ÿ 0xff 255

#DB0=18
#DB1=22
#DB2=23
#DB3=24
#DB4=25
#DB5=27
#DB6=30
#DB7=31

# GPIO numbers (BCM) for data lines, MSB to LSB
DATOS=[31,30,27,25,24,23,22,18]

#GPIO numbers (BCM) for control lines
RST=7
RD=8
RS=9
CS=10
WR=11

# LCD orientation (0,90º,180º,270º)
LCD_ORIENTATION0=0
LCD_ORIENTATION1=96
LCD_ORIENTATION2=160
LCD_ORIENTATION3=192

#ILI9163 commands
NOP=0x00
SOFT_RESET=0x01
GET_RED_CHANNEL=0x06
GET_GREEN_CHANNEL=0x07
GET_BLUE_CHANNEL=0x08
GET_PIXEL_FORMAT=0x0C
GET_POWER_MODE=0x0A
GET_ADDRESS_MODE=0x0B
GET_DISPLAY_MODE=0x0D
GET_SIGNAL_MODE=0x0E
GET_DIAGNOSTIC_RESULT=0x0F
ENTER_SLEEP_MODE=0x10
EXIT_SLEEP_MODE=0x11
ENTER_PARTIAL_MODE=0x12
ENTER_NORMAL_MODE=0x13
EXIT_INVERT_MODE=0x20
ENTER_INVERT_MODE=0x21
SET_GAMMA_CURVE=0x26
SET_DISPLAY_OFF=0x28
SET_DISPLAY_ON=0x29
SET_COLUMN_ADDRESS=0x2A
SET_PAGE_ADDRESS=0x2B
WRITE_MEMORY_START=0x2C
WRITE_LUT=0x2D
READ_MEMORY_START=0x2E
SET_PARTIAL_AREA=0x30
SET_SCROLL_AREA=0x33
SET_TEAR_OFF=0x34
SET_TEAR_ON=0x35
SET_ADDRESS_MODE=0x36
SET_SCROLL_START=0X37
EXIT_IDLE_MODE=0x38
ENTER_IDLE_MODE=0x39
SET_PIXEL_FORMAT=0x3A
WRITE_MEMORY_CONTINUE=0x3C
READ_MEMORY_CONTINUE=0x3E
SET_TEAR_SCANLINE=0x44
GET_SCANLINE=0x45
READ_ID1=0xDA
READ_ID2=0xDB
READ_ID3=0xDC
FRAME_RATE_CONTROL1=0xB1
FRAME_RATE_CONTROL2=0xB2
FRAME_RATE_CONTROL3=0xB3
DISPLAY_INVERSION=0xB4
SOURCE_DRIVER_DIRECTION=0xB7
GATE_DRIVER_DIRECTION=0xB8
POWER_CONTROL1=0xC0
POWER_CONTROL2=0xC1
POWER_CONTROL3=0xC2
POWER_CONTROL4=0xC3
POWER_CONTROL5=0xC4
VCOM_CONTROL1=0xC5
VCOM_CONTROL2=0xC6
VCOM_OFFSET_CONTROL=0xC7
WRITE_ID4_VALUE=0xD3
NV_MEMORY_FUNCTION1=0xD7
NV_MEMORY_FUNCTION2=0xDE
POSITIVE_GAMMA_CORRECT=0xE0
NEGATIVE_GAMMA_CORRECT=0xE1
GAM_R_SEL=0xF2

#GPIO initial setup
for x in range(0,8):
	GPIO.setup(DATOS[x],GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(RST,GPIO.OUT,initial=GPIO.HIGH)
GPIO.setup(RD,GPIO.OUT,initial=GPIO.HIGH)
GPIO.setup(RS,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(CS,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(WR,GPIO.OUT,initial=GPIO.HIGH)

#function to pack 3 bytes rgb value in 2 bytes, R,G and B must be <=31
def decode_rgb(r,g,b):
	return ((b<<11) | (g<<6) | (r))

#functions to translate x,y pixel coords. to text column,row	
def textX(x):
	return x*6
	
def textY(y):
	return y*8

#initial LCD reset	
def reset_LCD():
	GPIO.output(RST,False)
	sleep (0.05)
	GPIO.output(RST,True)
	sleep (0.12)
	return

#write command to controller
def write_command(address):
	GPIO.output(RS,False)
	GPIO.output(CS,False)
	for x in range(7,-1,-1): # sequence inverted to fill from LSB to MSB
		GPIO.output(DATOS[x],(address & 1))
		address>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)

#write parameter after command	
def write_parameter(parameter):
	GPIO.output(RS,True)
	GPIO.output(CS,False)
	for x in range(7,-1,-1):
		GPIO.output(DATOS[x],(parameter & 1))
		parameter>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)
#	GPIO.output(CS,True)

#write data to frame memory, two-byte colour value	
def write_data(dataByte1,dataByte2):
	GPIO.output(RS,True)
	GPIO.output(CS,False)
	for x in range(7,-1,-1):
		GPIO.output(DATOS[x],(dataByte1 & 1))
		dataByte1>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)
	for x in range(7,-1,-1):
		GPIO.output(DATOS[x],(dataByte2 & 1))
		dataByte2>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)
	
#-------------------------------------------	
	
def iniciar_LCD(orientacion):
	reset_LCD()
	write_command(EXIT_SLEEP_MODE)
	sleep(0.05)
	write_command(SET_PIXEL_FORMAT)
	write_parameter(0x05)
	write_command(SET_GAMMA_CURVE)
	write_parameter(0x04)
	write_command(GAM_R_SEL)
	write_parameter(0x01)
    
	write_command(POSITIVE_GAMMA_CORRECT)
	write_parameter(0x3f)
	write_parameter(0x25)
	write_parameter(0x1c)
	write_parameter(0x1e)
	write_parameter(0x20)
	write_parameter(0x12)
	write_parameter(0x2a)
	write_parameter(0x90)
	write_parameter(0x24)
	write_parameter(0x11)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x00)

	write_command(NEGATIVE_GAMMA_CORRECT)
	write_parameter(0x20)
	write_parameter(0x20)
	write_parameter(0x20)
	write_parameter(0x20)
	write_parameter(0x05)
	write_parameter(0x00)
	write_parameter(0x15)
	write_parameter(0xa7)
	write_parameter(0x3d)
	write_parameter(0x18)
	write_parameter(0x25)
	write_parameter(0x2a)
	write_parameter(0x2b)
	write_parameter(0x2b)
	write_parameter(0x3a)
        
	write_command(FRAME_RATE_CONTROL1)
	write_parameter(0x08)
	write_parameter(0x08)
    
	write_command(DISPLAY_INVERSION)
	write_parameter(0x01)
	
	write_command(POWER_CONTROL1)
	write_parameter(0x0a)
	write_parameter(0x02)
	
	write_command(POWER_CONTROL2)
	write_parameter(0x02)
	
	write_command(VCOM_CONTROL1)
	write_parameter(0x50)
	write_parameter(0x5b)
	
	write_command(VCOM_OFFSET_CONTROL)
	write_parameter(0x40)
	
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x7f)
	
	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x00)
	write_parameter(0x7f)

	write_command(SET_ADDRESS_MODE)
	write_parameter(orientacion)
	
	clear_display(decode_rgb(0,0,0))
	write_command(SET_DISPLAY_ON)
#	write_command(WRITE_MEMORY_START)
	
# clear display,writes same color pixel in all screen
def clear_display(color):
	color_hi=color>>8	#requires two 1-byte writes 
	color_lo= color&(~(65280))
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x02)
	write_parameter(0x00)
	write_parameter(0x81)
	
	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x01)
	write_parameter(0x00)
	write_parameter(0x80)

	write_command(WRITE_MEMORY_START)
	for pixel in range (0,16384):
		write_data(color_hi,color_lo)

# draw a dot in x,y with 'color' colour		
def draw_dot(x,y,color):
	color_hi=color>>8
	color_lo= color&(~(65280))
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(x+2)
	write_parameter(0x00)
	write_parameter(x+3)

	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(y+1)
	write_parameter(0x00)
	write_parameter(y+2)
	
	write_command(WRITE_MEMORY_START)
	write_data(color_hi,color_lo)

# Bresenham's algorithm to draw a line with integers
# x0<=x1, y0<=y1
def draw_line(x0,y0,x1,y1,color):
	dy=y1-y0
	dx=x1-x0
	if (dy<0):
		dy=-dy
		stepy=-1
	else:
		stepy=1
	if (dx<0):
		dx=-dx
		stepx=-1
	else:
		stepx=1
	dx <<=1
	dy <<=1
	draw_dot(x0,y0,color)
	if (dx>dy):
		fraccion=dy-(dx>>1)
		while (x0!=x1):
			if (fraccion>=0):
				y0 +=stepy
				fraccion -=dx
			x0 +=stepx
			fraccion +=dy
			draw_dot(x0,y0,color)
	else:
		fraccion=dx-(dy>>1)
		while (y0!=y1):
			if (fraccion>=0):
				x0 +=stepx
				fraccion -=dy
			y0 +=stepy
			fraccion +=dx
			draw_dot(x0,y0,color)

# draws hollow rectangle
# x0<=x1, y0<= y1 			
def draw_rectangle(x0,y0,x1,y1,color):
	draw_line(x0,y0,x0,y1,color)
	draw_line(x0,y1,x1,y1,color)
	draw_line(x1,y0,x1,y1,color)
	draw_line(x0,y0,x1,y0,color)

# draws filled rectangle, fills frame memory section with same pixel
# x0<=x1, y0<=y1
def draw_filled_rectangle(x0,y0,x1,y1,color):
	color_hi=color>>8
	color_lo= color&(~(65280))
	x0+=2
	y0+=1
	x1+=2
	y1+=1
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(x0)
	write_parameter(0x00)
	write_parameter(x1)
	
	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(y0)
	write_parameter(0x00)
	write_parameter(y1)
	
	write_command(WRITE_MEMORY_START)
	for pixels in range (0,(1+x1-x0)*(1+y1-y0)):
		write_data(color_hi,color_lo)
		
#Bresenham's circle algorithm, circle can't pass screen boundaries
def draw_circle(x0,y0,radio,color):
	error=1-radio
	errorx=1
	errory=-2*radio
	y=radio
	x=0
	draw_dot(x0,y0+radio,color)
	draw_dot(x0,y0-radio,color)
	draw_dot(x0+radio,y0,color)
	draw_dot(x0-radio,y0,color)
	while (x<y):
		if (error>=0):
			y -=1
			errory +=2
			error +=errory
		x +=1
		errorx +=2
		error +=errorx
		draw_dot(x0+x,y0+y,color)
		draw_dot(x0-x,y0+y,color)
		draw_dot(x0+x,y0-y,color)
		draw_dot(x0-x,y0-y,color)
		draw_dot(x0+y,y0+x,color)
		draw_dot(x0-y,y0+x,color)
		draw_dot(x0+y,y0-x,color)
		draw_dot(x0-y,y0-x,color)

# writes a character in graphic coordinates x,y, with 
# foreground and background colours
def put_char(character,x,y,fgcolor,bgcolor):
	fgcolor_hi=fgcolor>>8
	fgcolor_lo= fgcolor&(~(65280))
	bgcolor_hi=bgcolor>>8
	bgcolor_lo= bgcolor&(~(65280))
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(x+2)
	write_parameter(0x00)
	write_parameter((x+7))
	
	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(y+1)
	write_parameter(0x00)
	write_parameter(y+8)
	
	write_command(WRITE_MEMORY_START)
	for row in range (0,8):
		for column in range (0,6):
			if (font5x8[ord(character)][column]) & (1<<row):
				write_data(fgcolor_hi,fgcolor_lo)
			else:
				write_data(bgcolor_hi,bgcolor_lo)

# writes a string in graphic x,y coordinates, with
# foreground and background colours. If edge of screen is reached,
# it wraps to next text line to same starting x coord.
def put_string(cadena,x,y,fgcolor,bgcolor):
	origen=x
	for char_number in range (0,len(cadena)):
		if (x>121):
			x=origen
			y +=8
		if (y>120):
			break
		put_char(cadena[char_number],x,y,fgcolor,bgcolor)
		x +=6
		
# defines scroll area, row1=lines in top fixed area,
#row2= lines in bottom fixed area
def scroll_area(row1,row2):
	write_command(SET_COLUMN_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x02)
	write_parameter(0x00)
	write_parameter(0x81)

	write_command(SET_PAGE_ADDRESS)
	write_parameter(0x00)
	write_parameter(0x01)
	write_parameter(0x00)
	write_parameter(0x80)
	x=row1+1
	alto=128-(row1+row2)
	y=row2+1
	write_command(SET_SCROLL_AREA)
	write_parameter(0x00)
	write_parameter(x)
	write_parameter(0x00)
	write_parameter(alto)
	write_parameter(0x00)
	write_parameter(y)
	print x,alto,y

# starts vertical scroll, line=how many lines from the start
# of the memory frame will be scrolled
def scroll_start(line):		
	write_command(SET_SCROLL_START)
	write_parameter(0x00)
	write_parameter(line)

def invert_screen():
	write_command(ENTER_INVERT_MODE)
	
def normal_screen():
	write_command(EXIT_INVERT_MODE)
#----------------------------------------------------------

iniciar_LCD(LCD_ORIENTATION0)
posx=0
posy=0
for i in range (32,220):
	put_char(chr(i),posx,posy,decode_rgb(31,31,31),decode_rgb(0,0,0))
	posx+=6
	if posx>=121:
		posx=0
		posy+=8
for i in range (48,123):
	put_char(chr(i),posx,posy,decode_rgb(0,0,0),decode_rgb(31,31,31))
	posx+=6
	if posx>=121:
		posx=0
		posy+=8
scroll_area(0,0)
for i in range (1,132):
	scroll_start(i)
	sleep(0.01)
for i in range (132,1,-1):
	scroll_start(i)
	sleep (0.01)
put_string("<<<PULSAR ENTER>>>",textX(2),textY(15),decode_rgb(31,31,0),decode_rgb(31,0,0))		
raw_input("pulsar enter")
for i in range (0,5):
	invert_screen()
	sleep (0.5)
	normal_screen()
	sleep (0.5)
	
draw_filled_rectangle(0,0,128,64 ,decode_rgb(31,0,0))
draw_filled_rectangle(0,64,128,128,decode_rgb(0,0,0))
for i in range (4,32,4):
	draw_rectangle(i,i,128-i,64-i,decode_rgb(i-1,i-1,i-1))
draw_line(0,0,128,128,decode_rgb(0,31,0))
draw_line(0,128,128,0,decode_rgb(0,31,0))
draw_circle(64,64,63,decode_rgb(0,0,31))
draw_circle(64,64,53,decode_rgb(0,0,31))
draw_circle(64,64,43,decode_rgb(0,0,31))
draw_circle(64,64,33,decode_rgb(0,0,31))
put_string("Hello,World!",28,28,decode_rgb(0,31,0),decode_rgb(0,0,31))
draw_filled_rectangle(0,64,128,128,decode_rgb(0,0,0))
draw_rectangle(0,64,127,127,decode_rgb(0,0,31))

# bat and ball animation
ballX=64
ballY=96
ballSpeed=1
xDir=ballSpeed
yDir=ballSpeed

while(1):
	draw_filled_rectangle(ballX,ballY,ballX+2,ballY+2,decode_rgb(0,0,0))
	draw_filled_rectangle(ballX-2,122,ballX+4,124,decode_rgb(0,0,0))
	ballX +=xDir
	ballY +=yDir
	if (ballX>121):
		xDir=-ballSpeed
	if (ballX<4):
		xDir=ballSpeed
	if (ballY>120):
		yDir=-ballSpeed
	if (ballY<66):
		yDir=ballSpeed
	draw_filled_rectangle(ballX,ballY,ballX+2,ballY+2,decode_rgb(31,31,31))
	draw_filled_rectangle(ballX-2,122,ballX+4,124,decode_rgb(31,31,31))
#GPIO.cleanup()

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Tue Oct 22, 2013 10:05 am

@Antares (and Simon) well done getting this to work. In fact, brilliant work both. I can't see anything obviously inefficient in your (@Antares) code but I can make the following comments.

there are some sleeps in there, but only the same as in the original C so that shouldn't make any difference.

function calls are quite expensive in python so where you loop doing the same thing lots of times it will slow it down

Code: Select all

for i in range (32,220):
   put_char(chr(i),posx,posy,decode_rgb(31,31,31),decode_rgb(0,0,0))
   posx+=6
   if posx>=121:
      posx=0
      posy+=8
would be faster written as

Code: Select all

RGB31 = decode_rgb(31, 31, 31) #do these once as if they were static
RGB0 = decode_rgb(0, 0, 0)
for i in range (32,220):
   put_char(chr(i), posx, posy, RGB31, RGB0)
   posx += 6
   if posx >= 121:
      posx = 0
      posy += 8
And it would then be more efficient to take the first four lines of put_char and put it in decode_rgb (and modify wherever colours are used, accordingly)

You need to use "for x in range(7,-1,-1):" an awful lot and python is temporarily creating a list each time along with an additional function call to range() I would create a global variable BITS = [7, 6, 5, 4, 3, 2, 1, 0] and then use "for x in BITS"

You can profile python quite easily
python -m cProfile your_program.py > temp_01.txt
then look through temp_01.txt on a spreadsheet

You could convert your code quite easily to C compiled using cython, mainly you will get a big speed increase from defining types for the variables so the python interpreter doesn't have to keep checking!

It would probably be a good idea to split your file up into 'core' functions and demo, as Simon's ili9163lcd v. FTM144D01N_test
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Wed Oct 23, 2013 9:38 am

Hi, @paddyg, thanks for your answer. I didn't expect a reply after a week, and I was thinking of posting again, this time in the Python forum.

The sleep() commands are there because they are specified in the ILI9163 datasheet, to wait for the screen to settle after reset. A couple of other sleep() are in the scroll function, it's done in hardware and it's too fast to see it without them, and in invert_screen, for the same reasons (those two functions are the only ones that I wrote myself)

You are absolutely right about the decode_rgb function: for a typical GUI application there would be no more than tree or four colors, so it would be better to write something like this:

Code: Select all

RED=decode_rgb(31,0,0)
BLUE=decode_rgb(0,0,31)
GREEN=decode_rgb(0,31,0)
and so forth

I didn't knew about loops using range being so time consuming, I think I thought in terms of BASIc for...next. I'lll try your solution and I'll post the results. Also, I didn't new how to profile Python, I'll try it too. And talking about this, do you know of a debugger? I'm using Geany as an IDE on the Raspberry, and I've seen nothing about that. I've debugged the code old school style, using print and raw_input.

About splitting the code, I thought that for posting it, it would be easier in a single file, I'm aware that it's more readable as separate files.

I've heard about Cython, but never tried it. I'll give it also a go. I'm right now converting the circuit to I2C with a PCF8574, I want to see if serial writing makes a difference. Of course, first I have to learn how I2C works (and I have ordered a couple of SPI chips, also).

And the next thing that I want to try is WiringPi Python wrapper, I've seen benchmarks that are really good. I have one doubt, I don't know if function digitalWriteByte is allowed in the Python version (given that GPIO pins in the Raspberry are not contiguous, and writing to the register could overwrite other BCM outputs used for the main board)

As a side note, I'll try to post some photos of the screen working, and of my Pi's case,I made it from an old DVB-T case, and I fitted a GPIO connector at the back, so I could use it as a OpenElec media center beside my TV, removing the flat cable, or to interface with the breadboard (I just have one Pi, and it earns every mA that it eats)

Thanks again, you have given me something to think about for at least a week. I'll keep you informed of the improvements.


(edited to correct typo)

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Wed Oct 23, 2013 11:04 am

@Antares, I suspect that with a bit of streamlining with the set_char as outlined it will be much improved if, as you say, the bat and ball thing looks to run at a reasonable speed. (edit) I did do a test as some stage and the actual GPIO.output() takes a tiny amount of time, micro seconds. So I thinks it's just a matter of cleaning up the loops in python.

range() isn't really slow but it's a function that generates a list so each time you call it there's a little bit of overhead. In py3 it works as an iterator that generates the numbers you require as and when. I don't know how efficient the python interpreter is. Where there is a loop with static values like this it might (should) do something more efficient, even in-lining it. Easy enough to try variations till you find the fastest. As you do those 8 step loops lots of times you might check whether you would benefit from unwrapping them completely i.e.

Code: Select all

   for x in range(7,-1,-1):
      GPIO.output(DATOS[x],(parameter & 1))
      parameter>>=1
becomes
  GPIO.output(DATAOS[7], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[6], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[5], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[4], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[3], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[2], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[1], (parameter & 1) )
  parameter >>= 1
  GPIO.output(DATAOS[0], (parameter & 1) )
ugly though that looks! Also I don't know how fast bit shifting is in python but it looks like the GPIO library will treat values != 0 as on, I would try:

Code: Select all

...
  GPIO.output(DATAOS[4], (parameter & 8) )
  GPIO.output(DATAOS[3], (parameter & 16) )
  GPIO.output(DATAOS[2], (parameter & 32) )
...
I use geany, there is a python debugger 'import pdb' but I've always found it sufficient to stick 'print..' in as you've been doing, to see what's happening.

The main reason for splitting is that you could make the lcd functions 'cythonable' which you can do with an augmenting file as described here
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Sat Oct 26, 2013 11:36 am

Some images from the build:
Image
This is a close view of the LCD running the demo

Image

General view of the breadboard with the LCD adapter

Image

And this is the back of my custom Pi's case (made from a broken DVB-T receiver case) showing the GPIO back connector, with a standard IDE cable running to the breadboard, and PAL and audio ouputs, HDMI output is at the hidden left side, USB and Ethernet at the front

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Tue Oct 29, 2013 1:04 am

I've been trying to make the code faster, following @paddyg advice.For that, I have profiled the code (thanks again, paddyg), and I've learned to use Runsnakerun, for me it's clearer than the standard text file that cProfile outputs. First, here is the result for my original code:
Image
It can be seen that the most time consuming routine is write_data (as it has to write two bytes for each screen pixel). The data was like this:

Code: Select all

def write_data(dataByte1,dataByte2):
	GPIO.output(RS,True)
	GPIO.output(CS,False)
	for x in range(7,-1,-1):
		GPIO.output(DATOS[x],(dataByte1 & 1))
		dataByte1>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)
	for x in range(7,-1,-1):
		GPIO.output(DATOS[x],(dataByte2 & 1))
		dataByte2>>=1
	GPIO.output(WR,False)
	GPIO.output(WR,True)
It's a convoluted code, using arrays, for .. in range loops, bit shifting, etc..

So I modified the code, per @paddyg advice, and the same routine looks like this:

Code: Select all

def write_data(dataByte1,dataByte2):
	GPIO.output(RS,True)
	GPIO.output(CS,False)
	GPIO.output(DB0,dataByte1&1)
	GPIO.output(DB1,dataByte1&2)
	GPIO.output(DB2,dataByte1&4)
	GPIO.output(DB3,dataByte1&8)
	GPIO.output(DB4,dataByte1&16)
	GPIO.output(DB5,dataByte1&32)
	GPIO.output(DB6,dataByte1&64)
	GPIO.output(DB7,dataByte1&128)
	GPIO.output(WR,False)
	GPIO.output(WR,True)
	GPIO.output(DB0,dataByte2&1)
	GPIO.output(DB1,dataByte2&2)
	GPIO.output(DB2,dataByte2&4)
	GPIO.output(DB3,dataByte2&8)
	GPIO.output(DB4,dataByte2&16)
	GPIO.output(DB5,dataByte2&32)
	GPIO.output(DB6,dataByte2&64)
	GPIO.output(DB7,dataByte2&128)
	GPIO.output(WR,False)
	GPIO.output(WR,True)
It's much more readable, and the result is (after also changing write_command and write_parameter in the same way) that the demo program executes a 33% faster:
Image

A strange thing about this profiling is that about two thirds of the time spent executing write_data is not used writing to the GPIO ports. The only thing that remains are the bit operators (databyte2&128....). It was a surprise to me, I thought that GPIO writing would be the big roadblock.

As I can't see a faster way of writing to the GPIO, I tried the I2C way. I connected a PCF8574 to the Pi and adapted the code:

Code: Select all

def write_data(dataByte1,dataByte2):
	GPIO.output(RS,True)
	GPIO.output(CS,False)
	
	i2c.write_byte( i2c_addr, dataByte1 )

	GPIO.output(WR,False)
	GPIO.output(WR,True)
	
	i2c.write_byte( i2c_addr, dataByte2 )

	GPIO.output(WR,False)
	GPIO.output(WR,True)
Now, the code is even more readable and short. But it's slower, too:
Image
In fact, the demo program takes 50% more time than the original version. So, I fetched a way to make it as fast as posible, and I found how to change the I2C clock frequency. From the original 100 KHz, I changed it to 600 kHz, that's the fastest I can set it, at 700kHz, I got Input/output errors. The profile for the same code is like this:
Image
It's a little faster than the improved parallel code, but not much, say a 10% faster. So I'm now at a dead end.

I think I'll try Cython, as suggested also by @paddyg, and maybe WiringPi2, I hope that one of the two will make it usable. If not, it's time to learn C.

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Tue Oct 29, 2013 2:42 pm

@Antares, this is all really good stuff. I like the runsnakerun graphical outputs.

Did you try upping the speed on the GPIO version?

Cython would definitely be worth a go. Even if you wanted to include some C code (i.e. some of the original stuff you converted to python) using Cython as a wrapper for it is a very good way of making it accessible from python. But I think, with a bit of care and suitable type defs, you can generate C code from python that will run just as fast.

One thing I meant to mention before: install git and keep a remote repository on github, bitbucket google.code. There's a bit of a learning curve but it's worth it, it means you can let other people see the code easily, they can contribute if you want and you can branch different versions such as when seeing what's quickest. Also it's a backup system so you don't loose all that hard work.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Wed Oct 30, 2013 12:18 am

Just out of interest I tried running a loop either in python or a cythonized function (basically doing GPIO.output(pin, val & 1) 100000 times) It didn't speed things up very dramatically
12.6 13.7 14.8 16.1 16.4 16.6 17.2 17.8 18.1 18.6 av. 16.2
7.6 10.0 10.9 13.3 14.4 14.9 15.0 15.5 15.7 17.7 av. 13.5 micro seconds each output
may be better with faster clock speed.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Wed Oct 30, 2013 11:26 am

Paddyg, I don't understand your question about the speed with the GPIO version. What I've changed between the two wersions with I2c is the frequency for the I2c clock( how fast the clock pulses in SCL go), and that doesn't apply to GPIO (As I've got just one pcf8574, the lcd data inputs are now managed with i2c, but control inputs are still managed with GPIO.output) For me, it tops at 600 KHz with my setup; as you can see in the photos above, I use an IDE cable about 30 cm. long, plus jumper wires in the breadboard in two sizes, the longest are 15 cm. I imagine that in a real working environment, with short wires (less capacity), decoupling capacitors in the i2c signals and a properly shielded pcb, I could use higher frequencies. I'm seeing in the oscilloscope some serious overshoot in the rising edges, but the circuit seems to cope with it, I don't get any errors on the lcd up to the point, at 700 kHz, where the PI throws an Input/output error and refuses to run.
One thing that I forgot in my last post was that your suggestion to reduce the number of decode_rgb calls was also a big speed boost, as can be seen on the runsnake profiles. I've precomputed values for red,yellow, green,blue,cyan and magenta, and use them all along the code.

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Wed Oct 30, 2013 8:32 pm

Me being stupid. There is a clock function somewhere on the GPIO but I don't think it has any relevance to output and input. Ignore it!

With cython calling the RPi.GPIO there is probably an overhead converting to and from python. To speed it up properly you probably need to encapsulate a very small C function, you could install bcm2835 other ideas here.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Thu Oct 31, 2013 4:15 pm

To further check my suggestion about only needing a very simple subset of functionality I tried taking the Dom and Gert example from the page I linked to, and put a pinout() method accessed via Cython. Now when I run the equivalent test of before I get
0.04 0.04 0.08 0.10 0.10 0.11 0.11 0.11 0.12 0.12 av. 0.10 micro seconds
i.e. it's about 150 times as fast!
[edit when I run it without the X server started it averages 0.05 micro seconds]

The other thing that I haven't investigated is that it looks as though the output writes to all pins at once but is masked by a (1 << pin) so presumably it could do a parallel write more easily, which would be 8 x faster!

I used the following hacks and haven't really tested it properly apart from sticking a led on the appropriate pin and seeing it glimmer (5 million times per second!)
domgert_c.c (basically just copied, removed main and added pin_out() and put pin initialization into setup_io())

Code: Select all

#define BCM2708_PERI_BASE        0x20000000
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

int  mem_fd;
void *gpio_map;

// I/O access
volatile unsigned *gpio;


// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0

int pin_out(int pin, int val)
{
  if (val) GPIO_SET = 1 << pin;
  else GPIO_CLR = 1 << pin;
  return 0;
}

//
// Set up a memory regions to access GPIO
//
int setup_io()
{
   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      exit(-1);
   }

   /* mmap GPIO */
   gpio_map = mmap(
      NULL,             //Any adddress in our space will do
      BLOCK_SIZE,       //Map length
      PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory
      MAP_SHARED,       //Shared with other processes
      mem_fd,           //File to map
      GPIO_BASE         //Offset to GPIO peripheral
   );

   close(mem_fd); //No need to keep mem_fd open after mmap

   if (gpio_map == MAP_FAILED) {
      printf("mmap error %d\n", (int)gpio_map);//errno also set!
      exit(-1);
   }

   // Always use volatile pointer!
   gpio = (volatile unsigned *)gpio_map;

  // temp do this, really have a way to make it configurable
  INP_GPIO(23); // must use INP_GPIO before we can use OUT_GPIO
  OUT_GPIO(23);
  return 0;

} // setup_io
domgert.pyx

Code: Select all

cdef extern from "domgert_c.c":
  int setup_io()
  int pin_out(int pin, int val)

def setgpio():
  setup_io()
  
def pinout(int pin, int val):
  for i in xrange(500000):
    pin_out(pin, val)
    pin_out(pin, 0)
temp.py

Code: Select all

import time, math
from domgert import pinout, setgpio

setgpio()
pin = 23
val = 1
res = []
tot = 0
for i in range(10):
  tm = time.time()
  pinout(23, 1)
  dt = time.time() - tm
  res.append(math.floor(dt*100) / 100.0)
  tot += dt
res.sort()
print tot / 10.0, res
setup.py

Code: Select all

from distutils.core import setup
from Cython.Build import cythonize

setup(
  name = 'test gpio',
  ext_modules = cythonize("domgert.pyx"),
)
then in a terminal
$ python setup.py build_ext --inplace
for some reason this puts the domgert.so in a created subdirectory with the same name the current directory so I have to copy it up a level! There's probably a perfectly good python reason for this!
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Thu Oct 31, 2013 10:06 pm

That looks very promising, Paddyg. I will be in a trip from tomorrow to Sunday, but I'll look at it as soon as possible. I'll have to start learning something about Cython.

And I think you're right about the parallel write, if you write directly to the GPIO register and then enable it, it changes all the GPIO outputs at a time. Simon's original C code for the Atmel used it. I really think that the GPIO Python library should include some kind of parallel write (and read) instruction, since it's possible in C. Let's see if I can cobble together something useful, but I know even less C than Python.

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Thu Oct 31, 2013 11:27 pm

Yes, it certainly gives quite a boost. Of course I'm doing the 500,000 loops in Cython (aka C) so it's very fast (but that's what I was doing on my first speed up comparison too). When I make the loop in normal python I get an average of 2.59 micro seconds per gpio.output, so slower but still *much* faster than 16 micro seconds.

Also that last (2.59mics) time was for a slightly different version where I basically took the c_gpio.c from Ben Croston's (i.e. the standard RPi.GPIO library) here I then didn't alter a scrap of code, just put a simple Cython wrapper on it. This is a bit slower as Ben doesn't seem to have built the gpio_output as a #define macro and he does a / 32 and % 32 to stop writing into random memory. I might look at converting all of Ben's library to Cython as there might be occasions (such as your screen driver) where the speed will be significant.

With the parallel writing, generally I suppose you would still have the process of setting the relevant bits in the uint and that would take just about the same length of time. But in your specific application you could do a chunk of this as a one-off process, effectively combining the DATOS array with the font and command values
i.e. bits [31,30,27,25,24,23,22,18] combined with ENTER_PARTIAL_MODE=0x12 (=b00010010)
would give b 00000010010000000000000000000000
These could be generated during the setup routine

PS my raspberry pi doesn't have all those pins, they stop at 27!
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Sun Nov 03, 2013 11:17 pm

Paddyg, I have just returned from a 3 day trip (extended weekend here in Spain, friday was a national holiday) and I haven't had the time to look at your solution, just a quick note: I have GPIO 28, 29, 30 and 31 because at the GPIO port at the back of my home-made case, I have connected P1 and P5 from the Pi, in P5 (in rev. 2 boards) there are the 4 additional ports. As I use a 40 pin standard IDE cable, I even have some pins free for future uses.

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Thu Nov 07, 2013 8:33 pm

Since my last post, I've been trying to speed up the code converting it to Wiringpi2, and I think that it has reached the point where it's usable, in terms of speed. I chose Wiringpi instead of Cython because, a) I don't know C and I'm still learning Python, and b) Wiringpi offers some functions to directly handle I2C and SPI expanders (that will be the next step, because, while in some cases it's OK to use 8 GPIO for the data bus and 5 more for the control lines, in many others it lets you with too few GPIO for other devices).
So, I first wrote a new program using Wiringpi digitalWrite functions instead of GPIO.output, and found that there was very little difference in speed with the GPIO code, perhaps a little faster, but not too much. Then I tried replacing the digitalWrite calls in write_data, write_parameter and write_command with digitalWriteByte, when writing to the data bus, the control bus is still using digitalWrite. As digitalWriteByte writes simultaneously to the first 8 GPIO outpus (in Wiringpi numbering convention), I had to change the pin numbers I used. The result is that the demo program executes now almost 200% faster than the parallel GPIO version:

Code: Select all

def write_data(dataByte1,dataByte2):
	wiringpi.digitalWrite(RS,True)
	wiringpi.digitalWrite(CS,False)
	wiringpi.digitalWriteByte(dataByte1)
	wiringpi.digitalWrite(WR,False)
	wiringpi.digitalWrite(WR,True)
	wiringpi.digitalWriteByte(dataByte2)
	wiringpi.digitalWrite(WR,False)
	wiringpi.digitalWrite(WR,True)
	wiringpi.digitalWrite(CS,True)
Image
This is the GPIO version (edited with the same GPIO pins than the Wiringpi version)
Image
And this is the Wiringpi version.
As can be seen in the images, the write_data function spends 210 uS on the GPIO version, and just 90 on the Wiringpi version.
As profiling makes the execution slower, I wrote some calls to time() at the beginning and at the end of the clear_display function, and before and after the code that fills the screen with text. I found that (with some variations) clear_display takes now about 1.1 secs., and filling the screen with text about 1.2 . It's a good improvement over the near 4 secs. to clear the screen that I started with.

As I said, the next step will be using again the PCF8574, this time with Wiringpi (I think there is already an extension for this chip in the library). Then maybe I will try Cython, I have no doubt that it will be faster, but also a little more difficult to implement.

Edit: correction of the images URL, previously they pointed to low-res not readable versions
Last edited by Antares on Mon Nov 11, 2013 12:02 pm, edited 1 time in total.

User avatar
paddyg
Posts: 2330
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: 1.5" ILI9163 lcd screen working

Fri Nov 08, 2013 12:17 am

@Antares, I am continually impressed both by your assimilation and application of python and your flawless English. You will benefit from following the path you describe in that you will have viewed the same problem from (almost) all possible angles.

The Cython option doesn't really involve any C as such (as I said, I just copied the existing .c files into my directory unaltered) but one of the reasons python is slow is that it lets you not bother to define your variables up front but checks what type they are every time they are used. Cython allow you to use cdef to avoid this checking. I think you will find that you can easily get 2,000% speed increase this way, possibly much faster if you include a write_byte function.

PS all the gpio functionality involves the same basic chunk of code as I linked to before whether it's wiringpi or bcm2835 or RPi.GPIO the only way to speed it up (from python) is to either offload the work to another board such as you propose (or an arduino type) or to reduce the type checking process being done by python.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Antares
Posts: 13
Joined: Thu Oct 03, 2013 10:09 pm

Re: 1.5" ILI9163 lcd screen working

Mon Nov 11, 2013 12:04 pm

I've corrected the pictures URL in my last post, they were not readable because they pointed to low-res thumbnails instead of the real pictures

Gadjetnut
Posts: 17
Joined: Fri Apr 05, 2013 9:13 pm

Re: 1.5" ILI9163 lcd screen working

Sun Apr 19, 2015 4:44 am

I have this LCD:
https://www.dipmicro.com/store/LCD-NOKIA5110-RGB

It uses the ILI9163C driver which led me to this post. I am trying to get the Python program posted above working but am confused by the mapping of the LCD pins and the config in the following code:

Code: Select all

# GPIO numbers (BCM) for data lines, MSB to LSB
DATOS=[31,30,27,25,24,23,22,18]

#GPIO numbers (BCM) for control lines
RST=7
RD=8
RS=9
CS=10
WR=11
I have the following pins on the LCD:

SCK, SDA, A0, RESET, CS

Which I have mapped as follows:

SCK - WR
SDA - RS
A0 - RD
RESET - RST
CS - CS

The Datasheet for the LCD describes the LCD pins as follows:

PinNo. Signal I/O Discription
1 VDDIO P Power Supply for Logic Circuit (TYP1.8/ 2.8V)
2 VDDA P Power Supply for Analog Circuit (TYP 2.8V)
3 SDA I Data input in SPI mode
4 A0 I Register Select Signal(Low:command, High:data)
5 CS I Chip Select Signal (Low Active)
6 SCL I Synchronizing clock signal in SPI mode
7 GND P Ground
8 LCD_ID1 - Connected to VDD
9 RST I Reset Signal (Low Active)
10 GND P Ground
11 LED+ P Power Supply for LED(Anode)
12 LED- P Power Supply for LED(Cathode)
13 LCD_ID2 - Connected to GND

Return to “General discussion”