t

Dependencies:   DM_FATFileSystem DM_HttpServer DM_USBHost EthernetInterface USBDevice mbed-rpc mbed-rtos

Fork of DMSupport by Embedded Artists

Committer:
embeddedartists
Date:
Fri Jan 16 11:13:39 2015 +0100
Revision:
22:1a58a518435c
Child:
23:6afd6a716e80
- Updated SPIFI code to allow BIOS to id chips not yet supported by DMSupport
- Updated QSPIFileSystem to be chip independant. Erase block info from SPIFI.cpp
- Split BiosDisplayAndTouch into BiosDisplay and BiosTouch
- Added BiosLoader with common functionallity
- Removed BIOS code from DMBoard
- Added first version of a touch listener

Who changed what in which revision?

UserRevisionLine numberNew contents of line
embeddedartists 22:1a58a518435c 1 /*
embeddedartists 22:1a58a518435c 2 * Copyright 2014 Embedded Artists AB
embeddedartists 22:1a58a518435c 3 *
embeddedartists 22:1a58a518435c 4 * Licensed under the Apache License, Version 2.0 (the "License");
embeddedartists 22:1a58a518435c 5 * you may not use this file except in compliance with the License.
embeddedartists 22:1a58a518435c 6 * You may obtain a copy of the License at
embeddedartists 22:1a58a518435c 7 *
embeddedartists 22:1a58a518435c 8 * http://www.apache.org/licenses/LICENSE-2.0
embeddedartists 22:1a58a518435c 9 *
embeddedartists 22:1a58a518435c 10 * Unless required by applicable law or agreed to in writing, software
embeddedartists 22:1a58a518435c 11 * distributed under the License is distributed on an "AS IS" BASIS,
embeddedartists 22:1a58a518435c 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
embeddedartists 22:1a58a518435c 13 * See the License for the specific language governing permissions and
embeddedartists 22:1a58a518435c 14 * limitations under the License.
embeddedartists 22:1a58a518435c 15 */
embeddedartists 22:1a58a518435c 16
embeddedartists 22:1a58a518435c 17 #include "mbed.h"
embeddedartists 22:1a58a518435c 18 #include "BiosTouch.h"
embeddedartists 22:1a58a518435c 19 #include "BiosLoader.h"
embeddedartists 22:1a58a518435c 20 #include "DMBoard.h"
embeddedartists 22:1a58a518435c 21 #include "bios.h"
embeddedartists 22:1a58a518435c 22 #include "meas.h"
embeddedartists 22:1a58a518435c 23
embeddedartists 22:1a58a518435c 24 /******************************************************************************
embeddedartists 22:1a58a518435c 25 * Defines and typedefs
embeddedartists 22:1a58a518435c 26 *****************************************************************************/
embeddedartists 22:1a58a518435c 27
embeddedartists 22:1a58a518435c 28 #define SIG_NEW_DATA 0x1
embeddedartists 22:1a58a518435c 29 class TouchHandler {
embeddedartists 22:1a58a518435c 30 public:
embeddedartists 22:1a58a518435c 31 TouchHandler(bios_header_t* bios, void* biosData, int num) :
embeddedartists 22:1a58a518435c 32 _latest(NULL), _touchIRQ(P2_25), _bios(bios),
embeddedartists 22:1a58a518435c 33 _biosData(biosData), _haveData(false), _points(num),
embeddedartists 22:1a58a518435c 34 _thread(NULL), _listener(NULL) {}
embeddedartists 22:1a58a518435c 35 void handleTouchInterrupt();
embeddedartists 22:1a58a518435c 36 void changeTouchInterrupt(bool enable, bool rising);
embeddedartists 22:1a58a518435c 37 TouchPanel::TouchError read(touch_coordinate_t* coord, int num);
embeddedartists 22:1a58a518435c 38 void run();
embeddedartists 22:1a58a518435c 39 void setThread(Thread* t) { _thread = t; }
embeddedartists 22:1a58a518435c 40 FunctionPointer* setListener(FunctionPointer* listener);
embeddedartists 22:1a58a518435c 41 private:
embeddedartists 22:1a58a518435c 42 Mutex _mutex;
embeddedartists 22:1a58a518435c 43 touch_coordinate_t* _latest;
embeddedartists 22:1a58a518435c 44 InterruptIn _touchIRQ;
embeddedartists 22:1a58a518435c 45 bios_header_t* _bios;
embeddedartists 22:1a58a518435c 46 void* _biosData;
embeddedartists 22:1a58a518435c 47 bool _haveData; //TODO: improve
embeddedartists 22:1a58a518435c 48 int _points;
embeddedartists 22:1a58a518435c 49 Thread* _thread;
embeddedartists 22:1a58a518435c 50 FunctionPointer* _listener;
embeddedartists 22:1a58a518435c 51 };
embeddedartists 22:1a58a518435c 52
embeddedartists 22:1a58a518435c 53 /******************************************************************************
embeddedartists 22:1a58a518435c 54 * Local variables
embeddedartists 22:1a58a518435c 55 *****************************************************************************/
embeddedartists 22:1a58a518435c 56
embeddedartists 22:1a58a518435c 57 /******************************************************************************
embeddedartists 22:1a58a518435c 58 * Private Functions
embeddedartists 22:1a58a518435c 59 *****************************************************************************/
embeddedartists 22:1a58a518435c 60
embeddedartists 22:1a58a518435c 61 BiosTouch::BiosTouch() :
embeddedartists 22:1a58a518435c 62 _initialized(false),
embeddedartists 22:1a58a518435c 63 _haveInfo(false),
embeddedartists 22:1a58a518435c 64 _poweredOn(false),
embeddedartists 22:1a58a518435c 65 //_touchIRQ(P2_25),
embeddedartists 22:1a58a518435c 66 _bios(NULL),
embeddedartists 22:1a58a518435c 67 _biosData(NULL),
embeddedartists 22:1a58a518435c 68 _handlerThread(NULL),
embeddedartists 22:1a58a518435c 69 _handler(NULL),
embeddedartists 22:1a58a518435c 70 _supportsTouch(false)
embeddedartists 22:1a58a518435c 71 {
embeddedartists 22:1a58a518435c 72 }
embeddedartists 22:1a58a518435c 73
embeddedartists 22:1a58a518435c 74 BiosTouch::~BiosTouch()
embeddedartists 22:1a58a518435c 75 {
embeddedartists 22:1a58a518435c 76 // _bios and _biosData are deallocated by BiosLoader
embeddedartists 22:1a58a518435c 77
embeddedartists 22:1a58a518435c 78 if (_handlerThread != NULL) {
embeddedartists 22:1a58a518435c 79 delete _handlerThread;
embeddedartists 22:1a58a518435c 80 _handlerThread = NULL;
embeddedartists 22:1a58a518435c 81 }
embeddedartists 22:1a58a518435c 82 if (_handler != NULL) {
embeddedartists 22:1a58a518435c 83 delete _handler;
embeddedartists 22:1a58a518435c 84 _handler = NULL;
embeddedartists 22:1a58a518435c 85 }
embeddedartists 22:1a58a518435c 86 }
embeddedartists 22:1a58a518435c 87
embeddedartists 22:1a58a518435c 88 // Function called from the BIOS
embeddedartists 22:1a58a518435c 89 static void touchIrqEnabler(uint32_t arg, bool enable, bool rising)
embeddedartists 22:1a58a518435c 90 {
embeddedartists 22:1a58a518435c 91 ((TouchHandler*)arg)->changeTouchInterrupt(enable, rising);
embeddedartists 22:1a58a518435c 92 #if defined(DM_BOARD_ENABLE_MEASSURING_PINS)
embeddedartists 22:1a58a518435c 93 if (enable) {
embeddedartists 22:1a58a518435c 94 SET_MEAS_PIN_1();
embeddedartists 22:1a58a518435c 95 } else {
embeddedartists 22:1a58a518435c 96 CLR_MEAS_PIN_1();
embeddedartists 22:1a58a518435c 97 }
embeddedartists 22:1a58a518435c 98 #endif
embeddedartists 22:1a58a518435c 99 }
embeddedartists 22:1a58a518435c 100
embeddedartists 22:1a58a518435c 101 static void touchTask(void const* args)
embeddedartists 22:1a58a518435c 102 {
embeddedartists 22:1a58a518435c 103 ((TouchHandler*)args)->run();
embeddedartists 22:1a58a518435c 104 }
embeddedartists 22:1a58a518435c 105
embeddedartists 22:1a58a518435c 106
embeddedartists 22:1a58a518435c 107 void TouchHandler::run()
embeddedartists 22:1a58a518435c 108 {
embeddedartists 22:1a58a518435c 109 RtosLog* log = DMBoard::instance().logger();
embeddedartists 22:1a58a518435c 110 BiosError_t err;
embeddedartists 22:1a58a518435c 111
embeddedartists 22:1a58a518435c 112 _latest = (touch_coordinate_t*)malloc(_points*sizeof(touch_coordinate_t));
embeddedartists 22:1a58a518435c 113 if (_latest == NULL) {
embeddedartists 22:1a58a518435c 114 log->printf("Failed to allocate memory for touch events\n");
embeddedartists 22:1a58a518435c 115 mbed_die();
embeddedartists 22:1a58a518435c 116 }
embeddedartists 22:1a58a518435c 117 memset(_latest, 0, _points*sizeof(touch_coordinate_t));
embeddedartists 22:1a58a518435c 118 while(true) {
embeddedartists 22:1a58a518435c 119 Thread::signal_wait(SIG_NEW_DATA);
embeddedartists 22:1a58a518435c 120 // if (_haveData) {
embeddedartists 22:1a58a518435c 121 // _haveData = false;
embeddedartists 22:1a58a518435c 122 SET_MEAS_PIN_3();
embeddedartists 22:1a58a518435c 123 _bios->touchIrqHandler(_biosData);
embeddedartists 22:1a58a518435c 124 CLR_MEAS_PIN_3();
embeddedartists 22:1a58a518435c 125 //read
embeddedartists 22:1a58a518435c 126 _mutex.lock();
embeddedartists 22:1a58a518435c 127 err = _bios->touchRead(_biosData, _latest, _points);
embeddedartists 22:1a58a518435c 128 FunctionPointer* fp = _listener;
embeddedartists 22:1a58a518435c 129 _mutex.unlock();
embeddedartists 22:1a58a518435c 130 if (err == BiosError_Ok) {
embeddedartists 22:1a58a518435c 131 //notify registered callbacks
embeddedartists 22:1a58a518435c 132 if (fp != NULL) {
embeddedartists 22:1a58a518435c 133 SET_MEAS_PIN_4();
embeddedartists 22:1a58a518435c 134 fp->call();
embeddedartists 22:1a58a518435c 135 SET_MEAS_PIN_4();
embeddedartists 22:1a58a518435c 136 }
embeddedartists 22:1a58a518435c 137 } else {
embeddedartists 22:1a58a518435c 138 log->printf("Failed to read touch event, err = %d\n", err);
embeddedartists 22:1a58a518435c 139 }
embeddedartists 22:1a58a518435c 140 // }
embeddedartists 22:1a58a518435c 141 }
embeddedartists 22:1a58a518435c 142 //if (_latest != NULL) {
embeddedartists 22:1a58a518435c 143 // free(_latest);
embeddedartists 22:1a58a518435c 144 // _latest = NULL;
embeddedartists 22:1a58a518435c 145 //}
embeddedartists 22:1a58a518435c 146 }
embeddedartists 22:1a58a518435c 147
embeddedartists 22:1a58a518435c 148 TouchPanel::TouchError TouchHandler::read(touch_coordinate_t* coord, int num)
embeddedartists 22:1a58a518435c 149 {
embeddedartists 22:1a58a518435c 150 if (num > _points || num < 1) {
embeddedartists 22:1a58a518435c 151 return TouchPanel::TouchError_InvalidParam;
embeddedartists 22:1a58a518435c 152 }
embeddedartists 22:1a58a518435c 153 _mutex.lock();
embeddedartists 22:1a58a518435c 154 memcpy(coord, _latest, num*sizeof(touch_coordinate_t));
embeddedartists 22:1a58a518435c 155 _mutex.unlock();
embeddedartists 22:1a58a518435c 156
embeddedartists 22:1a58a518435c 157 return TouchPanel::TouchError_Ok;
embeddedartists 22:1a58a518435c 158 }
embeddedartists 22:1a58a518435c 159
embeddedartists 22:1a58a518435c 160 void TouchHandler::handleTouchInterrupt()
embeddedartists 22:1a58a518435c 161 {
embeddedartists 22:1a58a518435c 162 SET_MEAS_PIN_2();
embeddedartists 22:1a58a518435c 163 //_haveData = true;
embeddedartists 22:1a58a518435c 164 if (_thread != NULL) {
embeddedartists 22:1a58a518435c 165 _thread->signal_set(SIG_NEW_DATA);
embeddedartists 22:1a58a518435c 166 }
embeddedartists 22:1a58a518435c 167 CLR_MEAS_PIN_2();
embeddedartists 22:1a58a518435c 168 }
embeddedartists 22:1a58a518435c 169
embeddedartists 22:1a58a518435c 170 void TouchHandler::changeTouchInterrupt(bool enable, bool rising)
embeddedartists 22:1a58a518435c 171 {
embeddedartists 22:1a58a518435c 172 if (enable) {
embeddedartists 22:1a58a518435c 173 if (rising) {
embeddedartists 22:1a58a518435c 174 _touchIRQ.rise(this, &TouchHandler::handleTouchInterrupt);
embeddedartists 22:1a58a518435c 175 } else {
embeddedartists 22:1a58a518435c 176 _touchIRQ.fall(this, &TouchHandler::handleTouchInterrupt);
embeddedartists 22:1a58a518435c 177 }
embeddedartists 22:1a58a518435c 178 } else {
embeddedartists 22:1a58a518435c 179 if (rising) {
embeddedartists 22:1a58a518435c 180 _touchIRQ.rise(NULL);
embeddedartists 22:1a58a518435c 181 } else {
embeddedartists 22:1a58a518435c 182 _touchIRQ.fall(NULL);
embeddedartists 22:1a58a518435c 183 }
embeddedartists 22:1a58a518435c 184 }
embeddedartists 22:1a58a518435c 185 }
embeddedartists 22:1a58a518435c 186
embeddedartists 22:1a58a518435c 187 FunctionPointer* TouchHandler::setListener(FunctionPointer* listener)
embeddedartists 22:1a58a518435c 188 {
embeddedartists 22:1a58a518435c 189 _mutex.lock();
embeddedartists 22:1a58a518435c 190 FunctionPointer* old = _listener;
embeddedartists 22:1a58a518435c 191 _listener = listener;
embeddedartists 22:1a58a518435c 192 _mutex.unlock();
embeddedartists 22:1a58a518435c 193 return old;
embeddedartists 22:1a58a518435c 194 }
embeddedartists 22:1a58a518435c 195
embeddedartists 22:1a58a518435c 196
embeddedartists 22:1a58a518435c 197 /******************************************************************************
embeddedartists 22:1a58a518435c 198 * Public Functions
embeddedartists 22:1a58a518435c 199 *****************************************************************************/
embeddedartists 22:1a58a518435c 200
embeddedartists 22:1a58a518435c 201 BiosTouch::TouchError BiosTouch::init()
embeddedartists 22:1a58a518435c 202 {
embeddedartists 22:1a58a518435c 203 TouchError result = TouchError_Ok;
embeddedartists 22:1a58a518435c 204 if (!_initialized) {
embeddedartists 22:1a58a518435c 205 do {
embeddedartists 22:1a58a518435c 206 if (BiosLoader::instance().params(&_bios, &_biosData) != DMBoard::Ok) {
embeddedartists 22:1a58a518435c 207 result = TouchError_ConfigError;
embeddedartists 22:1a58a518435c 208 break;
embeddedartists 22:1a58a518435c 209 }
embeddedartists 22:1a58a518435c 210
embeddedartists 22:1a58a518435c 211 result = (TouchError)_bios->touchInformation(_biosData, &_supportsTouch, &_supportsTouchCalibration, &_touchIsResistive, &_touchNumFingers);
embeddedartists 22:1a58a518435c 212 if (result != TouchError_Ok) {
embeddedartists 22:1a58a518435c 213 break;
embeddedartists 22:1a58a518435c 214 }
embeddedartists 22:1a58a518435c 215 _haveInfo = true;
embeddedartists 22:1a58a518435c 216
embeddedartists 22:1a58a518435c 217 // is it supported at all?
embeddedartists 22:1a58a518435c 218 if (!_supportsTouch) {
embeddedartists 22:1a58a518435c 219 result = TouchError_TouchNotSupported;
embeddedartists 22:1a58a518435c 220 break;
embeddedartists 22:1a58a518435c 221 }
embeddedartists 22:1a58a518435c 222
embeddedartists 22:1a58a518435c 223 _handler = new TouchHandler(_bios, _biosData, _touchNumFingers);
embeddedartists 22:1a58a518435c 224
embeddedartists 22:1a58a518435c 225 result = (TouchError)_bios->touchInit(_biosData, touchIrqEnabler, (uint32_t)_handler);
embeddedartists 22:1a58a518435c 226 if (result != TouchError_Ok) {
embeddedartists 22:1a58a518435c 227 break;
embeddedartists 22:1a58a518435c 228 }
embeddedartists 22:1a58a518435c 229
embeddedartists 22:1a58a518435c 230 result = (TouchError)_bios->touchPowerUp(_biosData);
embeddedartists 22:1a58a518435c 231 if (result != TouchError_Ok) {
embeddedartists 22:1a58a518435c 232 break;
embeddedartists 22:1a58a518435c 233 }
embeddedartists 22:1a58a518435c 234
embeddedartists 22:1a58a518435c 235 _handlerThread = new Thread(touchTask, _handler);
embeddedartists 22:1a58a518435c 236 _handler->setThread(_handlerThread);
embeddedartists 22:1a58a518435c 237
embeddedartists 22:1a58a518435c 238 _initialized = true;
embeddedartists 22:1a58a518435c 239 } while(0);
embeddedartists 22:1a58a518435c 240
embeddedartists 22:1a58a518435c 241 if (!_initialized) {
embeddedartists 22:1a58a518435c 242 if (_handler != NULL) {
embeddedartists 22:1a58a518435c 243 delete _handler;
embeddedartists 22:1a58a518435c 244 _handler = NULL;
embeddedartists 22:1a58a518435c 245 }
embeddedartists 22:1a58a518435c 246 }
embeddedartists 22:1a58a518435c 247 }
embeddedartists 22:1a58a518435c 248 return result;
embeddedartists 22:1a58a518435c 249 }
embeddedartists 22:1a58a518435c 250
embeddedartists 22:1a58a518435c 251 BiosTouch::TouchError BiosTouch::read(touch_coordinate_t &coord)
embeddedartists 22:1a58a518435c 252 {
embeddedartists 22:1a58a518435c 253 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 254 if (!_initialized) {
embeddedartists 22:1a58a518435c 255 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 256 } else {
embeddedartists 22:1a58a518435c 257 //err = (TouchError)_bios->touchRead(_biosData, &coord, 1);
embeddedartists 22:1a58a518435c 258 err = _handler->read(&coord, 1);
embeddedartists 22:1a58a518435c 259 }
embeddedartists 22:1a58a518435c 260 return err;
embeddedartists 22:1a58a518435c 261 }
embeddedartists 22:1a58a518435c 262
embeddedartists 22:1a58a518435c 263 BiosTouch::TouchError BiosTouch::read(touch_coordinate_t* coord, int num)
embeddedartists 22:1a58a518435c 264 {
embeddedartists 22:1a58a518435c 265 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 266 if (!_initialized) {
embeddedartists 22:1a58a518435c 267 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 268 } else {
embeddedartists 22:1a58a518435c 269 //err = (TouchError)_bios->touchRead(_biosData, coord, num);
embeddedartists 22:1a58a518435c 270 err = _handler->read(coord, num);
embeddedartists 22:1a58a518435c 271 }
embeddedartists 22:1a58a518435c 272 return err;
embeddedartists 22:1a58a518435c 273 }
embeddedartists 22:1a58a518435c 274
embeddedartists 22:1a58a518435c 275 BiosTouch::TouchError BiosTouch::info(bool* resistive, int* maxPoints, bool* calibrated)
embeddedartists 22:1a58a518435c 276 {
embeddedartists 22:1a58a518435c 277 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 278 if (!_haveInfo) {
embeddedartists 22:1a58a518435c 279 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 280 } else {
embeddedartists 22:1a58a518435c 281 *resistive = _touchIsResistive;
embeddedartists 22:1a58a518435c 282 *maxPoints = _touchNumFingers;
embeddedartists 22:1a58a518435c 283 *calibrated = _supportsTouchCalibration;
embeddedartists 22:1a58a518435c 284 }
embeddedartists 22:1a58a518435c 285 return err;
embeddedartists 22:1a58a518435c 286 }
embeddedartists 22:1a58a518435c 287
embeddedartists 22:1a58a518435c 288 bool BiosTouch::isTouchSupported()
embeddedartists 22:1a58a518435c 289 {
embeddedartists 22:1a58a518435c 290 #if defined(DM_BOARD_USE_TOUCH)
embeddedartists 22:1a58a518435c 291 if (_haveInfo) {
embeddedartists 22:1a58a518435c 292 return _supportsTouch;
embeddedartists 22:1a58a518435c 293 }
embeddedartists 22:1a58a518435c 294 #endif
embeddedartists 22:1a58a518435c 295 return false;
embeddedartists 22:1a58a518435c 296 }
embeddedartists 22:1a58a518435c 297
embeddedartists 22:1a58a518435c 298 BiosTouch::TouchError BiosTouch::calibrateStart()
embeddedartists 22:1a58a518435c 299 {
embeddedartists 22:1a58a518435c 300 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 301 if (!_initialized) {
embeddedartists 22:1a58a518435c 302 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 303 } else {
embeddedartists 22:1a58a518435c 304 err = (TouchError)_bios->touchCalibrateStart(_biosData);
embeddedartists 22:1a58a518435c 305 }
embeddedartists 22:1a58a518435c 306 return err;
embeddedartists 22:1a58a518435c 307 }
embeddedartists 22:1a58a518435c 308
embeddedartists 22:1a58a518435c 309 BiosTouch::TouchError BiosTouch::getNextCalibratePoint(uint16_t* x, uint16_t* y, bool* last)
embeddedartists 22:1a58a518435c 310 {
embeddedartists 22:1a58a518435c 311 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 312 if (!_initialized) {
embeddedartists 22:1a58a518435c 313 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 314 } else {
embeddedartists 22:1a58a518435c 315 err = (TouchError)_bios->touchGetNextCalibPoint(_biosData, x, y, last);
embeddedartists 22:1a58a518435c 316 }
embeddedartists 22:1a58a518435c 317 return err;
embeddedartists 22:1a58a518435c 318 }
embeddedartists 22:1a58a518435c 319
embeddedartists 22:1a58a518435c 320 BiosTouch::TouchError BiosTouch::waitForCalibratePoint(bool* morePoints, uint32_t timeout)
embeddedartists 22:1a58a518435c 321 {
embeddedartists 22:1a58a518435c 322 TouchError err = TouchError_Ok;
embeddedartists 22:1a58a518435c 323 if (!_initialized) {
embeddedartists 22:1a58a518435c 324 err = TouchError_NoInit;
embeddedartists 22:1a58a518435c 325 } else {
embeddedartists 22:1a58a518435c 326 err = (TouchError)_bios->touchWaitForCalibratePoint(_biosData, morePoints, timeout);
embeddedartists 22:1a58a518435c 327 }
embeddedartists 22:1a58a518435c 328 return err;
embeddedartists 22:1a58a518435c 329 }
embeddedartists 22:1a58a518435c 330
embeddedartists 22:1a58a518435c 331 FunctionPointer* BiosTouch::setListener(FunctionPointer* listener)
embeddedartists 22:1a58a518435c 332 {
embeddedartists 22:1a58a518435c 333 if (_initialized) {
embeddedartists 22:1a58a518435c 334 return _handler->setListener(listener);
embeddedartists 22:1a58a518435c 335 }
embeddedartists 22:1a58a518435c 336 return NULL;
embeddedartists 22:1a58a518435c 337 }