Class Module for MMA845x I2C Accelerometer.

Dependents:   mDotEVBM2X MTDOT-EVBDemo-DRH MTDOT-BOX-EVB-Factory-Firmware-LIB-108 MTDOT-UDKDemo_Senet ... more

Fork of MMA845x by Sam Grove

Committer:
Evan Hosseini
Date:
Wed Jan 31 10:02:14 2018 -0600
Revision:
5:d662a7003b6f
Parent:
4:4ff1650da84c
Make driver thread safe by making i2c read transactions atomic

Who changed what in which revision?

UserRevisionLine numberNew contents of line
sam_grove 0:9d1e3a344e4f 1 /**
sam_grove 0:9d1e3a344e4f 2 * @file MMA845x.cpp
falingtrea 1:41af2b3eefb5 3 * @brief Device driver - MMA845X 3-axis accelerometer IC W/RTOS support
falingtrea 1:41af2b3eefb5 4 * @author Tim Barr
sam_grove 0:9d1e3a344e4f 5 * @version 1.0
sam_grove 0:9d1e3a344e4f 6 * @see http://cache.freescale.com/files/sensors/doc/data_sheet/MMA8451Q.pdf
sam_grove 0:9d1e3a344e4f 7 * @see http://cache.freescale.com/files/sensors/doc/data_sheet/MMA8452Q.pdf
sam_grove 0:9d1e3a344e4f 8 * @see http://cache.freescale.com/files/sensors/doc/data_sheet/MMA8453Q.pdf
sam_grove 0:9d1e3a344e4f 9 *
falingtrea 1:41af2b3eefb5 10 * Copyright (c) 2015
sam_grove 0:9d1e3a344e4f 11 *
sam_grove 0:9d1e3a344e4f 12 * Licensed under the Apache License, Version 2.0 (the "License");
sam_grove 0:9d1e3a344e4f 13 * you may not use this file except in compliance with the License.
sam_grove 0:9d1e3a344e4f 14 * You may obtain a copy of the License at
sam_grove 0:9d1e3a344e4f 15 *
sam_grove 0:9d1e3a344e4f 16 * http://www.apache.org/licenses/LICENSE-2.0
sam_grove 0:9d1e3a344e4f 17 *
sam_grove 0:9d1e3a344e4f 18 * Unless required by applicable law or agreed to in writing, software
sam_grove 0:9d1e3a344e4f 19 * distributed under the License is distributed on an "AS IS" BASIS,
sam_grove 0:9d1e3a344e4f 20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
sam_grove 0:9d1e3a344e4f 21 * See the License for the specific language governing permissions and
sam_grove 0:9d1e3a344e4f 22 * limitations under the License.
falingtrea 1:41af2b3eefb5 23 *
falingtrea 1:41af2b3eefb5 24 * 5/5/2015 Forked from https://developer.mbed.org/users/sam_grove/code/MMA845x/
falingtrea 1:41af2b3eefb5 25 *
falingtrea 1:41af2b3eefb5 26 * 6/20/2015 TAB Added setup functions and polling data capability. Also added RTOS calls
falingtrea 1:41af2b3eefb5 27 * TODO Still need to add interrupt support for other Accelerometer mode support
sam_grove 0:9d1e3a344e4f 28 */
mfiore 2:70df6adad015 29
sam_grove 0:9d1e3a344e4f 30 #include "MMA845x.h"
falingtrea 1:41af2b3eefb5 31 #include "mbed_debug.h"
falingtrea 1:41af2b3eefb5 32 #include "rtos.h"
mfiore 2:70df6adad015 33
falingtrea 1:41af2b3eefb5 34 MMA845x::MMA845x(I2C &i2c, SA0 const i2c_addr, InterruptIn* int1, InterruptIn* int2)
sam_grove 0:9d1e3a344e4f 35 {
sam_grove 0:9d1e3a344e4f 36 _i2c = &i2c;
falingtrea 1:41af2b3eefb5 37 _int1 = int1;
falingtrea 1:41af2b3eefb5 38 _int2 = int2;
mfiore 2:70df6adad015 39
falingtrea 1:41af2b3eefb5 40 _i2c_addr = (0x1c | i2c_addr) << 1;
mfiore 2:70df6adad015 41
sam_grove 0:9d1e3a344e4f 42 MMA845x::init();
sam_grove 0:9d1e3a344e4f 43
sam_grove 0:9d1e3a344e4f 44 return;
sam_grove 0:9d1e3a344e4f 45 }
sam_grove 0:9d1e3a344e4f 46
falingtrea 1:41af2b3eefb5 47 uint8_t MMA845x::init(void)
sam_grove 0:9d1e3a344e4f 48 {
falingtrea 1:41af2b3eefb5 49 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 50 uint8_t i = 0;
falingtrea 1:41af2b3eefb5 51 char reg_val[1];
mfiore 2:70df6adad015 52
falingtrea 1:41af2b3eefb5 53 _who_am_i = 0x00;
mfiore 2:70df6adad015 54
falingtrea 1:41af2b3eefb5 55 // Reset all registers to POR values
falingtrea 1:41af2b3eefb5 56 result = MMA845x::writeRegister(CTRL_REG2, 0xFF); //REG 0x2B
mfiore 2:70df6adad015 57 if (result == 0) {
falingtrea 1:41af2b3eefb5 58
mfiore 2:70df6adad015 59 do {
mfiore 2:70df6adad015 60 // wait for the reset bit to clear. readRegister may error out so we re-try 10 times
mfiore 2:70df6adad015 61 osDelay(200);
mfiore 2:70df6adad015 62 reg_val[0] = 0x40;
mfiore 2:70df6adad015 63 result = MMA845x::readRegister(CTRL_REG2,1,reg_val);
mfiore 2:70df6adad015 64 reg_val[0] = reg_val[0] & 0x40;
mfiore 2:70df6adad015 65 i++;
mfiore 2:70df6adad015 66 } while(((reg_val[0] != 0)||( result != 0)) && (i<=10));
falingtrea 1:41af2b3eefb5 67 }
falingtrea 1:41af2b3eefb5 68
mfiore 2:70df6adad015 69 if (result == 0) {
falingtrea 1:41af2b3eefb5 70 result = MMA845x::readRegister(WHO_AM_I,1,reg_val);
falingtrea 1:41af2b3eefb5 71 }
falingtrea 1:41af2b3eefb5 72
mfiore 2:70df6adad015 73 switch (reg_val[0]) {
mfiore 2:70df6adad015 74 case MMA8451:
mfiore 2:70df6adad015 75 case MMA8452:
mfiore 2:70df6adad015 76 case MMA8453:
mfiore 2:70df6adad015 77 _who_am_i= reg_val[0];
mfiore 2:70df6adad015 78 if ((_int1 == NULL) && (_int2 == NULL))
mfiore 2:70df6adad015 79 _polling_mode = true;
mfiore 2:70df6adad015 80 else _polling_mode = false;
mfiore 2:70df6adad015 81 break;
mfiore 2:70df6adad015 82 default:
mfiore 2:70df6adad015 83 debug ("Device not supported by this library!\n\r");
mfiore 2:70df6adad015 84 result = 1;
falingtrea 1:41af2b3eefb5 85 }
falingtrea 1:41af2b3eefb5 86
mfiore 2:70df6adad015 87 if(result != 0) {
falingtrea 1:41af2b3eefb5 88 debug("MMA845x:init failed\n\r");
falingtrea 1:41af2b3eefb5 89 }
mfiore 2:70df6adad015 90
falingtrea 1:41af2b3eefb5 91
mfiore 2:70df6adad015 92 return result;
falingtrea 1:41af2b3eefb5 93 }
falingtrea 1:41af2b3eefb5 94
falingtrea 1:41af2b3eefb5 95 uint8_t MMA845x::setCommonParameters(RANGE range, RESOLUTION resolution, LOW_NOISE lo_noise,
mfiore 2:70df6adad015 96 DATA_RATE data_rate, OVERSAMPLE_MODE os_mode, HPF_MODE hpf_mode) const
falingtrea 1:41af2b3eefb5 97 {
falingtrea 1:41af2b3eefb5 98 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 99 char datain[1];
falingtrea 1:41af2b3eefb5 100 uint8_t dataout = 0;
falingtrea 1:41af2b3eefb5 101
falingtrea 1:41af2b3eefb5 102 result |= MMA845x::readRegister(SYSMOD,1,datain); // Make sure MMA845x is in Stand-By mode
mfiore 2:70df6adad015 103 if ((datain[0] & 0x03) != 0 ) {
mfiore 2:70df6adad015 104 debug ("MMA845x not in STAND BY mode\n\f");
falingtrea 1:41af2b3eefb5 105 debug("MMA845x:setCommonParameters failed\n\r");
falingtrea 1:41af2b3eefb5 106 result = 1;
falingtrea 1:41af2b3eefb5 107 return result;
falingtrea 1:41af2b3eefb5 108 }
falingtrea 1:41af2b3eefb5 109
falingtrea 1:41af2b3eefb5 110 result |= MMA845x::readRegister(CTRL_REG1, 1, datain);
falingtrea 1:41af2b3eefb5 111 dataout = (datain[0] & 0xB1) | resolution | lo_noise | data_rate;
falingtrea 1:41af2b3eefb5 112 result |= MMA845x::writeRegister(CTRL_REG1, dataout); // Set resolution, Low Noise mode, and data rate
falingtrea 1:41af2b3eefb5 113
falingtrea 1:41af2b3eefb5 114 result |= MMA845x::readRegister(CTRL_REG2,1, datain);
falingtrea 1:41af2b3eefb5 115 dataout = (datain[0] & 0xFB) | os_mode;
falingtrea 1:41af2b3eefb5 116 result |= MMA845x::writeRegister(CTRL_REG2, dataout); // Set Oversample mode
falingtrea 1:41af2b3eefb5 117
falingtrea 1:41af2b3eefb5 118 result |= MMA845x::readRegister(XYZ_DATA_CFG,1, datain);
falingtrea 1:41af2b3eefb5 119 dataout = range | hpf_mode;
falingtrea 1:41af2b3eefb5 120 result |= MMA845x::writeRegister(XYZ_DATA_CFG, dataout); //Set HPF mode and range
falingtrea 1:41af2b3eefb5 121
falingtrea 1:41af2b3eefb5 122 // result |= MMA845x::readRegister(HP_FILTER_CUTOFF,1, datain);
falingtrea 1:41af2b3eefb5 123 // result |= MMA845x::writeRegister(HP_FILTER_CUTOFF, dataout); //REG 0xF HPF settings
falingtrea 1:41af2b3eefb5 124
mfiore 2:70df6adad015 125 if(result != 0) {
falingtrea 1:41af2b3eefb5 126 debug("MMA845x:setParameters failed\n\r");
falingtrea 1:41af2b3eefb5 127 }
mfiore 2:70df6adad015 128
mfiore 2:70df6adad015 129 return result;
mfiore 2:70df6adad015 130
falingtrea 1:41af2b3eefb5 131 }
falingtrea 1:41af2b3eefb5 132
falingtrea 1:41af2b3eefb5 133 uint8_t MMA845x::enableMotionDetect(void) const
falingtrea 1:41af2b3eefb5 134 {
falingtrea 1:41af2b3eefb5 135 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 136 return result;
mfiore 2:70df6adad015 137 }
mfiore 2:70df6adad015 138
falingtrea 1:41af2b3eefb5 139 uint8_t MMA845x::enablePulseDetect(void) const
falingtrea 1:41af2b3eefb5 140 {
falingtrea 1:41af2b3eefb5 141 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 142 return result;
mfiore 2:70df6adad015 143 }
falingtrea 1:41af2b3eefb5 144
falingtrea 1:41af2b3eefb5 145 uint8_t MMA845x::enableOrientationDetect(void) const
falingtrea 1:41af2b3eefb5 146 {
falingtrea 1:41af2b3eefb5 147 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 148
mfiore 2:70df6adad015 149 if(_who_am_i != MMA8451) {
falingtrea 1:41af2b3eefb5 150 debug("%s %d: Feature not compatible with the connected device.\n", __FILE__, __LINE__);
falingtrea 1:41af2b3eefb5 151 result = 1;
falingtrea 1:41af2b3eefb5 152 }
mfiore 2:70df6adad015 153
falingtrea 1:41af2b3eefb5 154 return result;
falingtrea 1:41af2b3eefb5 155 }
falingtrea 1:41af2b3eefb5 156
falingtrea 1:41af2b3eefb5 157 uint8_t MMA845x::enableTransientDetect(void) const
falingtrea 1:41af2b3eefb5 158 {
falingtrea 1:41af2b3eefb5 159 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 160 return result;
mfiore 2:70df6adad015 161 }
mfiore 2:70df6adad015 162
falingtrea 1:41af2b3eefb5 163 uint8_t MMA845x::enableAutoSleep(void) const
falingtrea 1:41af2b3eefb5 164 {
falingtrea 1:41af2b3eefb5 165 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 166 return result;
mfiore 2:70df6adad015 167 }
falingtrea 1:41af2b3eefb5 168
falingtrea 1:41af2b3eefb5 169 uint8_t MMA845x::enableFIFO(void) const
falingtrea 1:41af2b3eefb5 170 {
falingtrea 1:41af2b3eefb5 171 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 172
mfiore 2:70df6adad015 173 if(_who_am_i != MMA8451) {
falingtrea 1:41af2b3eefb5 174 debug("%s %d: Feature not compatible with the connected device.\n", __FILE__, __LINE__);
falingtrea 1:41af2b3eefb5 175 result = 1;
falingtrea 1:41af2b3eefb5 176 }
mfiore 2:70df6adad015 177
falingtrea 1:41af2b3eefb5 178 return result;
sam_grove 0:9d1e3a344e4f 179 }
sam_grove 0:9d1e3a344e4f 180
falingtrea 1:41af2b3eefb5 181 uint8_t MMA845x::activeMode(void) const
falingtrea 1:41af2b3eefb5 182 {
falingtrea 1:41af2b3eefb5 183 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 184 char datain[1];
falingtrea 1:41af2b3eefb5 185 uint8_t dataout;
falingtrea 1:41af2b3eefb5 186
falingtrea 1:41af2b3eefb5 187 result |= MMA845x::readRegister(CTRL_REG1,1, datain);
falingtrea 1:41af2b3eefb5 188 dataout = (datain[0] & 0xFE) | 0x01 ;
falingtrea 1:41af2b3eefb5 189 result |= MMA845x::writeRegister(CTRL_REG1, dataout); // Set to active mode
falingtrea 1:41af2b3eefb5 190
falingtrea 1:41af2b3eefb5 191 return result;
mfiore 2:70df6adad015 192 }
falingtrea 1:41af2b3eefb5 193 uint8_t MMA845x::standbyMode(void) const
falingtrea 1:41af2b3eefb5 194 {
falingtrea 1:41af2b3eefb5 195 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 196 char datain[1];
falingtrea 1:41af2b3eefb5 197 uint8_t dataout;
falingtrea 1:41af2b3eefb5 198
falingtrea 1:41af2b3eefb5 199 result |= MMA845x::readRegister(CTRL_REG1,1, datain);
falingtrea 1:41af2b3eefb5 200 dataout = (datain[0] & 0xFE);
falingtrea 1:41af2b3eefb5 201 result |= MMA845x::writeRegister(CTRL_REG1, dataout); // Set to standby mode
falingtrea 1:41af2b3eefb5 202
falingtrea 1:41af2b3eefb5 203 return result;
mfiore 2:70df6adad015 204 }
falingtrea 1:41af2b3eefb5 205
falingtrea 1:41af2b3eefb5 206 uint8_t MMA845x::getStatus(void) const
sam_grove 0:9d1e3a344e4f 207 {
falingtrea 1:41af2b3eefb5 208 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 209 char datain[1];
falingtrea 1:41af2b3eefb5 210 uint8_t dataout;
falingtrea 1:41af2b3eefb5 211
falingtrea 1:41af2b3eefb5 212 result = MMA845x::readRegister(STATUS,1, datain);
falingtrea 1:41af2b3eefb5 213
falingtrea 1:41af2b3eefb5 214 if (result != 0)
mfiore 2:70df6adad015 215 dataout = result;
falingtrea 1:41af2b3eefb5 216 else
mfiore 2:70df6adad015 217 dataout = datain[0];
falingtrea 1:41af2b3eefb5 218
falingtrea 1:41af2b3eefb5 219 return dataout;
mfiore 2:70df6adad015 220 }
falingtrea 1:41af2b3eefb5 221
falingtrea 1:41af2b3eefb5 222 int16_t MMA845x::getX(void)
falingtrea 1:41af2b3eefb5 223 {
falingtrea 1:41af2b3eefb5 224 char datain[2];
falingtrea 1:41af2b3eefb5 225
mfiore 2:70df6adad015 226 if (_polling_mode) {
falingtrea 1:41af2b3eefb5 227 MMA845x::readRegister(OUT_X_MSB,2, datain);
falingtrea 1:41af2b3eefb5 228 _data._x = ((datain[0] << 8) | datain[1]); /* data is 14 bit signed with 2 LSB = 0 */
falingtrea 1:41af2b3eefb5 229 _data._x /= 4; /* need to shift first to preserve sign then /4 to remove LSBs */
falingtrea 1:41af2b3eefb5 230 }
mfiore 2:70df6adad015 231 return _data._x;
falingtrea 1:41af2b3eefb5 232
sam_grove 0:9d1e3a344e4f 233 }
mfiore 2:70df6adad015 234
falingtrea 1:41af2b3eefb5 235 int16_t MMA845x::getY(void)
sam_grove 0:9d1e3a344e4f 236 {
falingtrea 1:41af2b3eefb5 237 char datain[2];
falingtrea 1:41af2b3eefb5 238
mfiore 2:70df6adad015 239 if (_polling_mode) {
falingtrea 1:41af2b3eefb5 240 MMA845x::readRegister(OUT_Y_MSB,2, datain);
falingtrea 1:41af2b3eefb5 241 _data._y = ((datain[0] << 8) | datain[1]); /* data is 14 bit signed with 2 LSB = 0 */
falingtrea 1:41af2b3eefb5 242 _data._y /= 4; /* need to shift first to preserve sign then /4 to remove LSBs */
falingtrea 1:41af2b3eefb5 243 }
mfiore 2:70df6adad015 244 return _data._y;
sam_grove 0:9d1e3a344e4f 245 }
sam_grove 0:9d1e3a344e4f 246
falingtrea 1:41af2b3eefb5 247 int16_t MMA845x::getZ(void)
sam_grove 0:9d1e3a344e4f 248 {
falingtrea 1:41af2b3eefb5 249 char datain[2];
falingtrea 1:41af2b3eefb5 250
mfiore 2:70df6adad015 251 if (_polling_mode) {
falingtrea 1:41af2b3eefb5 252 MMA845x::readRegister(OUT_Z_MSB,2, datain);
falingtrea 1:41af2b3eefb5 253 _data._z = ((datain[0] << 8) | datain[1]); /* data is 14 bit signed with 2 LSB = 0 */
falingtrea 1:41af2b3eefb5 254 _data._z /= 4; /* need to shift first to preserve sign then /4 to remove LSBs */
falingtrea 1:41af2b3eefb5 255 }
falingtrea 1:41af2b3eefb5 256
sam_grove 0:9d1e3a344e4f 257 return _data._z;
sam_grove 0:9d1e3a344e4f 258 }
mfiore 2:70df6adad015 259
falingtrea 1:41af2b3eefb5 260 MMA845x_DATA MMA845x::getXYZ(void)
sam_grove 0:9d1e3a344e4f 261 {
falingtrea 1:41af2b3eefb5 262 char datain[6];
falingtrea 1:41af2b3eefb5 263
mfiore 2:70df6adad015 264 if (_polling_mode) {
falingtrea 1:41af2b3eefb5 265 MMA845x::readRegister(OUT_X_MSB,6, datain); /* data is 14 bit signed with 2 LSB = 0 */
falingtrea 1:41af2b3eefb5 266 _data._x = ((datain[0] << 8) | datain[1]); /* need to shift first to preserve sign */
falingtrea 1:41af2b3eefb5 267 _data._x /= 4; /* then /4 to remove LSBs */
falingtrea 1:41af2b3eefb5 268 _data._y = ((datain[2] << 8) | datain[3]);
falingtrea 1:41af2b3eefb5 269 _data._y /= 4;
falingtrea 1:41af2b3eefb5 270 _data._z = ((datain[4] << 8) | datain[5]);
falingtrea 1:41af2b3eefb5 271 _data._z /= 4;
falingtrea 1:41af2b3eefb5 272 }
falingtrea 1:41af2b3eefb5 273
sam_grove 0:9d1e3a344e4f 274 return _data;
sam_grove 0:9d1e3a344e4f 275 }
sam_grove 0:9d1e3a344e4f 276
falingtrea 1:41af2b3eefb5 277 char MMA845x::getWhoAmI(void) const
falingtrea 1:41af2b3eefb5 278 {
falingtrea 1:41af2b3eefb5 279 return _who_am_i;
falingtrea 1:41af2b3eefb5 280 }
falingtrea 1:41af2b3eefb5 281
falingtrea 1:41af2b3eefb5 282 uint8_t MMA845x::writeRegister(uint8_t const reg, uint8_t const data) const
sam_grove 0:9d1e3a344e4f 283 {
sam_grove 0:9d1e3a344e4f 284 char buf[2] = {reg, data};
sam_grove 0:9d1e3a344e4f 285 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 286
falingtrea 1:41af2b3eefb5 287 buf[0] = reg;
falingtrea 1:41af2b3eefb5 288 buf[1] = data;
mfiore 2:70df6adad015 289
falingtrea 1:41af2b3eefb5 290 result |= _i2c->write(_i2c_addr, buf, 2);
mfiore 2:70df6adad015 291
Evan Hosseini 5:d662a7003b6f 292 if (result != 0) {
Evan Hosseini 5:d662a7003b6f 293 debug("MMA845x::writeRegister failed r-%d\n\r",result);
sam_grove 0:9d1e3a344e4f 294 }
mfiore 2:70df6adad015 295
falingtrea 1:41af2b3eefb5 296 return result;
sam_grove 0:9d1e3a344e4f 297 }
sam_grove 0:9d1e3a344e4f 298
falingtrea 1:41af2b3eefb5 299 uint8_t MMA845x::readRegister(uint8_t const reg, uint8_t count, char* data) const
sam_grove 0:9d1e3a344e4f 300 {
falingtrea 1:41af2b3eefb5 301 uint8_t result = 0;
falingtrea 1:41af2b3eefb5 302 char reg_out[1];
mfiore 2:70df6adad015 303
falingtrea 1:41af2b3eefb5 304 reg_out[0] = reg;
Evan Hosseini 5:d662a7003b6f 305 _i2c->lock();
Evan Hosseini 5:d662a7003b6f 306
Evan Hosseini 5:d662a7003b6f 307 // MMA8451Q expects a repeated start from the master
mfiore 2:70df6adad015 308 result |= _i2c->write(_i2c_addr,reg_out,1,true);
falingtrea 1:41af2b3eefb5 309
Evan Hosseini 5:d662a7003b6f 310 if (result != 0) {
falingtrea 1:41af2b3eefb5 311 debug("MMA845x::readRegister failed write r- %d\n\r", result);
Evan Hosseini 5:d662a7003b6f 312 goto exit;
sam_grove 0:9d1e3a344e4f 313 }
mfiore 2:70df6adad015 314
mfiore 2:70df6adad015 315 result |= _i2c->read(_i2c_addr,data,count,false);
mfiore 2:70df6adad015 316
Evan Hosseini 5:d662a7003b6f 317 if (result != 0) {
falingtrea 1:41af2b3eefb5 318 debug("MMA845x::readRegister failed read r-%d\n\r",result);
sam_grove 0:9d1e3a344e4f 319 }
mfiore 2:70df6adad015 320
Evan Hosseini 5:d662a7003b6f 321 exit:
Evan Hosseini 5:d662a7003b6f 322 _i2c->unlock();
falingtrea 1:41af2b3eefb5 323 return result;
sam_grove 0:9d1e3a344e4f 324 }