Updated for more display types. Fixed memoryaddress confusion in address() method. Added new getAddress() method. Added support for UDCs, Backlight control and other features such as control through I2C and SPI port expanders and controllers with native I2C and SPI interfaces. Refactored to fix issue with pins that are default declared as NC.

Dependents:   GPSDevice TestTextLCD SD to Flash Data Transfer DrumMachine ... more

Fork of TextLCD by Simon Ford

Example

Hello World! for the TextLCD

#include "mbed.h"
#include "TextLCD.h"
 
// Host PC Communication channels
Serial pc(USBTX, USBRX); // tx, rx
 
// I2C Communication
I2C i2c_lcd(p28,p27); // SDA, SCL
 
// SPI Communication
SPI spi_lcd(p5, NC, p7); // MOSI, MISO, SCLK

//TextLCD lcd(p15, p16, p17, p18, p19, p20);                // RS, E, D4-D7, LCDType=LCD16x2, BL=NC, E2=NC, LCDTCtrl=HD44780
//TextLCD_SPI lcd(&spi_lcd, p8, TextLCD::LCD40x4);   // SPI bus, 74595 expander, CS pin, LCD Type  
TextLCD_I2C lcd(&i2c_lcd, 0x42, TextLCD::LCD20x4);  // I2C bus, PCF8574 Slaveaddress, LCD Type
//TextLCD_I2C lcd(&i2c_lcd, 0x42, TextLCD::LCD16x2, TextLCD::WS0010); // I2C bus, PCF8574 Slaveaddress, LCD Type, Device Type
//TextLCD_SPI_N lcd(&spi_lcd, p8, p9);               // SPI bus, CS pin, RS pin, LCDType=LCD16x2, BL=NC, LCDTCtrl=ST7032_3V3   
//TextLCD_I2C_N lcd(&i2c_lcd, ST7032_SA, TextLCD::LCD16x2, NC, TextLCD::ST7032_3V3); // I2C bus, Slaveaddress, LCD Type, BL=NC, LCDTCtrl=ST7032_3V3  

int main() {
    pc.printf("LCD Test. Columns=%d, Rows=%d\n\r", lcd.columns(), lcd.rows());
    
    for (int row=0; row<lcd.rows(); row++) {
      int col=0;
      
      pc.printf("MemAddr(Col=%d, Row=%d)=0x%02X\n\r", col, row, lcd.getAddress(col, row));      
//      lcd.putc('-');
      lcd.putc('0' + row);      
      
      for (col=1; col<lcd.columns()-1; col++) {    
        lcd.putc('*');
      }
 
      pc.printf("MemAddr(Col=%d, Row=%d)=0x%02X\n\r", col, row, lcd.getAddress(col, row));      
      lcd.putc('+');
        
    }    
    
// Show cursor as blinking character
    lcd.setCursor(TextLCD::CurOff_BlkOn);
 
// Set and show user defined characters. A maximum of 8 UDCs are supported by the HD44780.
// They are defined by a 5x7 bitpattern. 
    lcd.setUDC(0, (char *) udc_0);  // Show |>
    lcd.putc(0);    
    lcd.setUDC(1, (char *) udc_1);  // Show <|
    lcd.putc(1);    

}

Handbook page

More info is here

Committer:
wim
Date:
Mon Feb 04 21:48:24 2013 +0000
Revision:
10:dd9b3a696acd
Parent:
9:0893d986e717
Child:
11:9ec02df863a1
Added support for 24x4 LCDs using KS0078 controller; Added Cursor On/Off

Who changed what in which revision?

UserRevisionLine numberNew contents of line
simon 1:ac48b187213c 1 /* mbed TextLCD Library, for a 4-bit LCD based on HD44780
simon 6:e4cb7ddee0d3 2 * Copyright (c) 2007-2010, sford, http://mbed.org
wim 8:03116f75b66e 3 * 2013, WH, Updated LCD types and fixed lcd address issues
simon 1:ac48b187213c 4 *
simon 1:ac48b187213c 5 * Permission is hereby granted, free of charge, to any person obtaining a copy
simon 1:ac48b187213c 6 * of this software and associated documentation files (the "Software"), to deal
simon 1:ac48b187213c 7 * in the Software without restriction, including without limitation the rights
simon 1:ac48b187213c 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
simon 1:ac48b187213c 9 * copies of the Software, and to permit persons to whom the Software is
simon 1:ac48b187213c 10 * furnished to do so, subject to the following conditions:
simon 1:ac48b187213c 11 *
simon 1:ac48b187213c 12 * The above copyright notice and this permission notice shall be included in
simon 1:ac48b187213c 13 * all copies or substantial portions of the Software.
simon 1:ac48b187213c 14 *
simon 1:ac48b187213c 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
simon 1:ac48b187213c 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
simon 1:ac48b187213c 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
simon 1:ac48b187213c 18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
simon 1:ac48b187213c 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
simon 1:ac48b187213c 20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
simon 1:ac48b187213c 21 * THE SOFTWARE.
simon 1:ac48b187213c 22 */
simon 1:ac48b187213c 23
simon 1:ac48b187213c 24 #include "TextLCD.h"
simon 1:ac48b187213c 25 #include "mbed.h"
simon 1:ac48b187213c 26
simon 7:44f34c09bd37 27 TextLCD::TextLCD(PinName rs, PinName e, PinName d4, PinName d5,
simon 7:44f34c09bd37 28 PinName d6, PinName d7, LCDType type) : _rs(rs),
simon 7:44f34c09bd37 29 _e(e), _d(d4, d5, d6, d7),
simon 1:ac48b187213c 30 _type(type) {
simon 1:ac48b187213c 31
simon 1:ac48b187213c 32 _e = 1;
simon 1:ac48b187213c 33 _rs = 0; // command mode
simon 1:ac48b187213c 34
simon 1:ac48b187213c 35 wait(0.015); // Wait 15ms to ensure powered up
simon 1:ac48b187213c 36
simon 1:ac48b187213c 37 // send "Display Settings" 3 times (Only top nibble of 0x30 as we've got 4-bit bus)
simon 1:ac48b187213c 38 for (int i=0; i<3; i++) {
simon 1:ac48b187213c 39 writeByte(0x3);
simon 1:ac48b187213c 40 wait(0.00164); // this command takes 1.64ms, so wait for it
simon 1:ac48b187213c 41 }
simon 1:ac48b187213c 42 writeByte(0x2); // 4-bit mode
simon 1:ac48b187213c 43 wait(0.000040f); // most instructions take 40us
simon 1:ac48b187213c 44
wim 10:dd9b3a696acd 45 // Display is now in 4-bit mode
wim 10:dd9b3a696acd 46 switch (_type) {
wim 10:dd9b3a696acd 47 case LCD8x1:
wim 10:dd9b3a696acd 48 writeCommand(0x20); // Function set 001 BW N F - -
wim 10:dd9b3a696acd 49 // N=0 (1 line)
wim 10:dd9b3a696acd 50 // F=0 (5x7 dots font)
wim 10:dd9b3a696acd 51 break;
wim 10:dd9b3a696acd 52
wim 10:dd9b3a696acd 53 case LCD24x4:
wim 10:dd9b3a696acd 54 // Special mode for KS0078
wim 10:dd9b3a696acd 55 writeCommand(0x2A); // Function set 001 BW N RE DH REV
wim 10:dd9b3a696acd 56 // N=1 (Dont care for KS0078)
wim 10:dd9b3a696acd 57 // RE=0 (Extended Regs, special mode for KS0078)
wim 10:dd9b3a696acd 58 // DH=1 (Disp shift, special mode for KS0078)
wim 10:dd9b3a696acd 59 // REV=0 (Reverse, special mode for KS0078)
wim 10:dd9b3a696acd 60
wim 10:dd9b3a696acd 61 writeCommand(0x2E); // Function set 001 BW N RE DH REV
wim 10:dd9b3a696acd 62 // N=1 (Dont care for KS0078)
wim 10:dd9b3a696acd 63 // RE=1 (Ena Extended Regs, special mode for KS0078)
wim 10:dd9b3a696acd 64 // DH=1 (Disp shift, special mode for KS0078)
wim 10:dd9b3a696acd 65 // REV=0 (Reverse, special mode for KS0078)
wim 10:dd9b3a696acd 66
wim 10:dd9b3a696acd 67 writeCommand(0x09); // Ext Function set 0000 1 FW BW NW
wim 10:dd9b3a696acd 68 // FW=0 (5-dot font, special mode for KS0078)
wim 10:dd9b3a696acd 69 // BW=0 (Cur BW invert disable, special mode for KS0078)
wim 10:dd9b3a696acd 70 // NW=1 (4 Line, special mode for KS0078)
wim 10:dd9b3a696acd 71
wim 10:dd9b3a696acd 72 writeCommand(0x2A); // Function set 001 BW N RE DH REV
wim 10:dd9b3a696acd 73 // N=1 (Dont care for KS0078)
wim 10:dd9b3a696acd 74 // RE=0 (Dis. Extended Regs, special mode for KS0078)
wim 10:dd9b3a696acd 75 // DH=1 (Disp shift, special mode for KS0078)
wim 10:dd9b3a696acd 76 // REV=0 (Reverse, special mode for KS0078)
wim 10:dd9b3a696acd 77 break;
wim 10:dd9b3a696acd 78
wim 10:dd9b3a696acd 79 default:
wim 10:dd9b3a696acd 80 writeCommand(0x28); // Function set 001 BW N F - -
wim 10:dd9b3a696acd 81 // N=1 (2 lines)
wim 10:dd9b3a696acd 82 // F=0 (5x7 dots font)
wim 10:dd9b3a696acd 83
wim 10:dd9b3a696acd 84 break;
wim 10:dd9b3a696acd 85 }
wim 10:dd9b3a696acd 86
wim 10:dd9b3a696acd 87 writeCommand(0x0C); // Display Ctrl 0000 1 D C B
wim 10:dd9b3a696acd 88 // Display On, Cursor Off, Blink Off
wim 10:dd9b3a696acd 89 _cursor = CurOff;
wim 10:dd9b3a696acd 90
wim 10:dd9b3a696acd 91 writeCommand(0x06); // Entry Mode 0000 01 CD S
wim 10:dd9b3a696acd 92 // Cursor Direction and Display Shift
wim 10:dd9b3a696acd 93 // CD=1 (Cur incr)
wim 10:dd9b3a696acd 94 // S=0 (No display shift)
wim 10:dd9b3a696acd 95
wim 10:dd9b3a696acd 96 cls();
simon 1:ac48b187213c 97 }
simon 1:ac48b187213c 98
wim 8:03116f75b66e 99
simon 1:ac48b187213c 100 void TextLCD::character(int column, int row, int c) {
wim 8:03116f75b66e 101 int addr = getAddress(column, row);
wim 8:03116f75b66e 102
wim 8:03116f75b66e 103 writeCommand(0x80 | addr);
simon 1:ac48b187213c 104 writeData(c);
simon 1:ac48b187213c 105 }
simon 1:ac48b187213c 106
wim 8:03116f75b66e 107
simon 1:ac48b187213c 108 void TextLCD::cls() {
simon 1:ac48b187213c 109 writeCommand(0x01); // cls, and set cursor to 0
simon 1:ac48b187213c 110 wait(0.00164f); // This command takes 1.64 ms
simon 1:ac48b187213c 111 locate(0, 0);
simon 1:ac48b187213c 112 }
simon 1:ac48b187213c 113
simon 1:ac48b187213c 114 void TextLCD::locate(int column, int row) {
simon 1:ac48b187213c 115 _column = column;
simon 1:ac48b187213c 116 _row = row;
simon 1:ac48b187213c 117 }
simon 1:ac48b187213c 118
simon 1:ac48b187213c 119 int TextLCD::_putc(int value) {
simon 1:ac48b187213c 120 if (value == '\n') {
simon 1:ac48b187213c 121 _column = 0;
simon 1:ac48b187213c 122 _row++;
simon 1:ac48b187213c 123 if (_row >= rows()) {
simon 1:ac48b187213c 124 _row = 0;
simon 1:ac48b187213c 125 }
simon 1:ac48b187213c 126 } else {
simon 1:ac48b187213c 127 character(_column, _row, value);
simon 1:ac48b187213c 128 _column++;
simon 1:ac48b187213c 129 if (_column >= columns()) {
simon 1:ac48b187213c 130 _column = 0;
simon 1:ac48b187213c 131 _row++;
simon 1:ac48b187213c 132 if (_row >= rows()) {
simon 1:ac48b187213c 133 _row = 0;
simon 1:ac48b187213c 134 }
simon 1:ac48b187213c 135 }
simon 1:ac48b187213c 136 }
simon 1:ac48b187213c 137 return value;
simon 1:ac48b187213c 138 }
simon 1:ac48b187213c 139
simon 1:ac48b187213c 140 int TextLCD::_getc() {
simon 1:ac48b187213c 141 return -1;
simon 1:ac48b187213c 142 }
simon 1:ac48b187213c 143
simon 1:ac48b187213c 144 void TextLCD::writeByte(int value) {
simon 1:ac48b187213c 145 _d = value >> 4;
simon 1:ac48b187213c 146 wait(0.000040f); // most instructions take 40us
simon 1:ac48b187213c 147 _e = 0;
simon 1:ac48b187213c 148 wait(0.000040f);
simon 1:ac48b187213c 149 _e = 1;
simon 1:ac48b187213c 150 _d = value >> 0;
simon 1:ac48b187213c 151 wait(0.000040f);
simon 1:ac48b187213c 152 _e = 0;
simon 1:ac48b187213c 153 wait(0.000040f); // most instructions take 40us
simon 1:ac48b187213c 154 _e = 1;
simon 1:ac48b187213c 155 }
simon 1:ac48b187213c 156
simon 1:ac48b187213c 157 void TextLCD::writeCommand(int command) {
simon 1:ac48b187213c 158 _rs = 0;
simon 1:ac48b187213c 159 writeByte(command);
simon 1:ac48b187213c 160 }
simon 1:ac48b187213c 161
simon 1:ac48b187213c 162 void TextLCD::writeData(int data) {
simon 1:ac48b187213c 163 _rs = 1;
simon 1:ac48b187213c 164 writeByte(data);
simon 1:ac48b187213c 165 }
simon 1:ac48b187213c 166
wim 8:03116f75b66e 167
wim 8:03116f75b66e 168 #if (0)
wim 8:03116f75b66e 169 // This is the original method.
wim 8:03116f75b66e 170 // It is confusing since it returns the memoryaddress or-ed with the set memorycommand 0x80.
wim 8:03116f75b66e 171 // Left it in here for compatibility with older code. New applications should use getAddress() instead.
wim 8:03116f75b66e 172 //
simon 1:ac48b187213c 173 int TextLCD::address(int column, int row) {
simon 1:ac48b187213c 174 switch (_type) {
simon 1:ac48b187213c 175 case LCD20x4:
simon 1:ac48b187213c 176 switch (row) {
simon 1:ac48b187213c 177 case 0:
simon 1:ac48b187213c 178 return 0x80 + column;
simon 1:ac48b187213c 179 case 1:
simon 1:ac48b187213c 180 return 0xc0 + column;
simon 1:ac48b187213c 181 case 2:
simon 1:ac48b187213c 182 return 0x94 + column;
simon 1:ac48b187213c 183 case 3:
simon 1:ac48b187213c 184 return 0xd4 + column;
simon 1:ac48b187213c 185 }
simon 1:ac48b187213c 186 case LCD16x2B:
simon 4:bf5b706f8d32 187 return 0x80 + (row * 40) + column;
simon 1:ac48b187213c 188 case LCD16x2:
simon 1:ac48b187213c 189 case LCD20x2:
simon 1:ac48b187213c 190 default:
simon 4:bf5b706f8d32 191 return 0x80 + (row * 0x40) + column;
simon 1:ac48b187213c 192 }
simon 1:ac48b187213c 193 }
wim 8:03116f75b66e 194 #endif
wim 8:03116f75b66e 195
wim 8:03116f75b66e 196
wim 8:03116f75b66e 197 // This replaces the original method.
wim 8:03116f75b66e 198 // Left it in here for compatibility with older code. New applications should use getAddress() instead.
wim 8:03116f75b66e 199 int TextLCD::address(int column, int row) {
wim 8:03116f75b66e 200 return 0x80 | getAddress(column, row);
wim 8:03116f75b66e 201 }
wim 8:03116f75b66e 202
wim 8:03116f75b66e 203 // This is new method to return the memory address based on row, column and displaytype.
wim 8:03116f75b66e 204 //
wim 8:03116f75b66e 205 int TextLCD::getAddress(int column, int row) {
wim 8:03116f75b66e 206
wim 8:03116f75b66e 207 switch (_type) {
wim 8:03116f75b66e 208 case LCD8x1:
wim 8:03116f75b66e 209 return 0x00 + column;
wim 8:03116f75b66e 210
wim 8:03116f75b66e 211 case LCD16x4:
wim 8:03116f75b66e 212 switch (row) {
wim 8:03116f75b66e 213 case 0:
wim 8:03116f75b66e 214 return 0x00 + column;
wim 8:03116f75b66e 215 case 1:
wim 8:03116f75b66e 216 return 0x40 + column;
wim 8:03116f75b66e 217 case 2:
wim 8:03116f75b66e 218 return 0x10 + column;
wim 8:03116f75b66e 219 case 3:
wim 8:03116f75b66e 220 return 0x50 + column;
wim 8:03116f75b66e 221 }
wim 8:03116f75b66e 222
wim 8:03116f75b66e 223 case LCD20x4:
wim 8:03116f75b66e 224 switch (row) {
wim 8:03116f75b66e 225 case 0:
wim 8:03116f75b66e 226 return 0x00 + column;
wim 8:03116f75b66e 227 case 1:
wim 8:03116f75b66e 228 return 0x40 + column;
wim 8:03116f75b66e 229 case 2:
wim 8:03116f75b66e 230 return 0x14 + column;
wim 8:03116f75b66e 231 case 3:
wim 8:03116f75b66e 232 return 0x54 + column;
wim 8:03116f75b66e 233 }
wim 8:03116f75b66e 234
wim 10:dd9b3a696acd 235 // Special mode for KS0078
wim 10:dd9b3a696acd 236 case LCD24x4:
wim 10:dd9b3a696acd 237 switch (row) {
wim 10:dd9b3a696acd 238 case 0:
wim 10:dd9b3a696acd 239 return 0x00 + column;
wim 10:dd9b3a696acd 240 case 1:
wim 10:dd9b3a696acd 241 return 0x20 + column;
wim 10:dd9b3a696acd 242 case 2:
wim 10:dd9b3a696acd 243 return 0x40 + column;
wim 10:dd9b3a696acd 244 case 3:
wim 10:dd9b3a696acd 245 return 0x60 + column;
wim 10:dd9b3a696acd 246 }
wim 10:dd9b3a696acd 247
wim 8:03116f75b66e 248 // Not sure about this one, seems wrong.
wim 8:03116f75b66e 249 case LCD16x2B:
wim 8:03116f75b66e 250 return 0x00 + (row * 40) + column;
wim 8:03116f75b66e 251
wim 8:03116f75b66e 252 case LCD8x2:
wim 8:03116f75b66e 253 case LCD16x2:
wim 8:03116f75b66e 254 case LCD20x2:
wim 8:03116f75b66e 255 case LCD24x2:
wim 9:0893d986e717 256 case LCD40x2:
wim 8:03116f75b66e 257 return 0x00 + (row * 0x40) + column;
wim 8:03116f75b66e 258
wim 8:03116f75b66e 259 // Should never get here.
wim 8:03116f75b66e 260 default:
wim 8:03116f75b66e 261 return 0x00;
wim 8:03116f75b66e 262 }
wim 8:03116f75b66e 263 }
wim 8:03116f75b66e 264
wim 8:03116f75b66e 265
wim 8:03116f75b66e 266 // Added for consistency. Set row, colum and update memoryaddress.
wim 8:03116f75b66e 267 //
wim 8:03116f75b66e 268 void TextLCD::setAddress(int column, int row) {
wim 8:03116f75b66e 269
wim 8:03116f75b66e 270 locate(column, row);
wim 8:03116f75b66e 271
wim 8:03116f75b66e 272 int addr = getAddress(column, row);
wim 8:03116f75b66e 273
wim 8:03116f75b66e 274 writeCommand(0x80 | addr);
wim 8:03116f75b66e 275 }
simon 1:ac48b187213c 276
simon 1:ac48b187213c 277 int TextLCD::columns() {
simon 1:ac48b187213c 278 switch (_type) {
wim 8:03116f75b66e 279 case LCD8x1:
wim 8:03116f75b66e 280 case LCD8x2:
wim 8:03116f75b66e 281 return 8;
wim 8:03116f75b66e 282
simon 1:ac48b187213c 283 case LCD16x2:
simon 1:ac48b187213c 284 case LCD16x2B:
wim 8:03116f75b66e 285 case LCD16x4:
wim 8:03116f75b66e 286 return 16;
wim 8:03116f75b66e 287
wim 8:03116f75b66e 288 case LCD20x2:
wim 8:03116f75b66e 289 case LCD20x4:
wim 8:03116f75b66e 290 return 20;
wim 8:03116f75b66e 291
wim 8:03116f75b66e 292 case LCD24x2:
wim 10:dd9b3a696acd 293 case LCD24x4:
wim 8:03116f75b66e 294 return 24;
wim 9:0893d986e717 295
wim 9:0893d986e717 296 case LCD40x2:
wim 9:0893d986e717 297 return 40;
wim 8:03116f75b66e 298
wim 8:03116f75b66e 299 // Should never get here.
simon 1:ac48b187213c 300 default:
wim 8:03116f75b66e 301 return 0;
simon 1:ac48b187213c 302 }
simon 1:ac48b187213c 303 }
simon 1:ac48b187213c 304
simon 1:ac48b187213c 305 int TextLCD::rows() {
simon 1:ac48b187213c 306 switch (_type) {
wim 8:03116f75b66e 307 case LCD8x1:
wim 8:03116f75b66e 308 return 1;
wim 8:03116f75b66e 309
wim 8:03116f75b66e 310 case LCD8x2:
simon 1:ac48b187213c 311 case LCD16x2:
simon 1:ac48b187213c 312 case LCD16x2B:
simon 1:ac48b187213c 313 case LCD20x2:
wim 8:03116f75b66e 314 case LCD24x2:
wim 9:0893d986e717 315 case LCD40x2:
wim 8:03116f75b66e 316 return 2;
wim 8:03116f75b66e 317
wim 8:03116f75b66e 318 case LCD16x4:
wim 8:03116f75b66e 319 case LCD20x4:
wim 10:dd9b3a696acd 320 case LCD24x4:
wim 8:03116f75b66e 321 return 4;
wim 8:03116f75b66e 322
simon 1:ac48b187213c 323 default:
wim 8:03116f75b66e 324 return 0;
simon 1:ac48b187213c 325 }
simon 1:ac48b187213c 326 }
wim 10:dd9b3a696acd 327
wim 10:dd9b3a696acd 328
wim 10:dd9b3a696acd 329 TextLCD::LCDCursor TextLCD::cursor(TextLCD::LCDCursor show) {
wim 10:dd9b3a696acd 330 LCDCursor cur = _cursor;
wim 10:dd9b3a696acd 331
wim 10:dd9b3a696acd 332 switch (show) {
wim 10:dd9b3a696acd 333 case CurOn : writeCommand(0x0F); // Cursor on and Blink char
wim 10:dd9b3a696acd 334 wait_us(40);
wim 10:dd9b3a696acd 335 _cursor = show;
wim 10:dd9b3a696acd 336 break;
wim 10:dd9b3a696acd 337 case CurOff : writeCommand(0x0C);
wim 10:dd9b3a696acd 338 wait_us(40);
wim 10:dd9b3a696acd 339 _cursor = show;
wim 10:dd9b3a696acd 340 break;
wim 10:dd9b3a696acd 341 default :
wim 10:dd9b3a696acd 342 break;
wim 10:dd9b3a696acd 343
wim 10:dd9b3a696acd 344 }
wim 10:dd9b3a696acd 345 return cur;
wim 10:dd9b3a696acd 346 }
wim 10:dd9b3a696acd 347
wim 10:dd9b3a696acd 348
wim 10:dd9b3a696acd 349
wim 10:dd9b3a696acd 350