Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: DM_HttpServer DM_USBHost
Dependents: lpc4088_displaymodule_emwin lpc4088_displaymodule_demo_sphere sampleGUI sampleEmptyGUI ... more
Fork of DMSupport by
Revision 41:e06e764ff4fd, committed 2019-10-23
- Comitter:
- embeddedartists
- Date:
- Wed Oct 23 06:59:29 2019 +0000
- Parent:
- 40:6df4f63aa406
- Child:
- 42:bbfe299d4a0c
- Commit message:
- Updates related to mbed OS 5
Changed in this revision
--- a/Bios/BiosLoader.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Bios/BiosLoader.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -223,6 +223,11 @@
if (!_initialized) {
do {
+ // With mbed OS 5 the MPU has been enabled and by default execution from
+ // RAM is not allowed. Calling this function to allow execution since
+ // the BIOS code will be executed from RAM.
+ mbed_mpu_manager_lock_ram_execution();
+
// Get the display bios from the DMBoard. DMBoard will have verified it
// and will keep it in RAM so there is no need to copy it.
uint8_t* p = NULL;
@@ -279,7 +284,7 @@
}
DMBoard::instance().logger()->printf("BIOS info: %s\n", msg);
#endif
-
+
// Prepare the BIOS instance data before calling the first function
BiosError_t e = _bios.initParams(_biosData, SystemCoreClock, PeripheralClock, wait_us, readTimeMs);
if (e != BiosError_Ok) {
@@ -291,7 +296,7 @@
NVIC_DisableIRQ(I2C0_IRQn);
NVIC_SetVector(I2C0_IRQn, (uint32_t)loader_i2c0_irq_handler);
NVIC_EnableIRQ(I2C0_IRQn);
-
+
_initialized = true;
} while(0);
}
@@ -303,9 +308,9 @@
DMBoard::instance().logger()->printf("BiosLoader::resetI2C()\n");
DigitalOut reset(P0_23);
reset = 0;
- wait_ms(1);
+ ThisThread::sleep_for(1);
reset = 1;
- wait_ms(10);
+ ThisThread::sleep_for(10);
}
--- a/DMBoard.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/DMBoard.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -137,7 +137,7 @@
#endif
#if defined(DM_BOARD_USE_DISPLAY)
- if (BiosDisplay::instance().init() != Display::DisplayError_Ok) {
+ if (display()->init() != Display::DisplayError_Ok) {
err = DisplayError;
break;
}
@@ -189,7 +189,7 @@
_buzzer = 0.5;
}
if (duration_ms > 0) {
- Thread::wait(duration_ms);
+ ThisThread::sleep_for(duration_ms);
_buzzer = 0;
}
}
--- a/DM_FATFileSystem.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://developer.mbed.org/teams/Embedded-Artists/code/DM_FATFileSystem/#edfd01fea6d7
--- a/DM_HttpServer.lib Wed Jun 10 09:54:15 2015 +0000 +++ b/DM_HttpServer.lib Wed Oct 23 06:59:29 2019 +0000 @@ -1,1 +1,1 @@ -http://developer.mbed.org/teams/Embedded-Artists/code/DM_HttpServer/#10b4d4075fbb +http://developer.mbed.org/teams/Embedded-Artists/code/DM_HttpServer/#c1c8276af541
--- a/DM_USBHost.lib Wed Jun 10 09:54:15 2015 +0000 +++ b/DM_USBHost.lib Wed Oct 23 06:59:29 2019 +0000 @@ -1,1 +1,1 @@ -http://developer.mbed.org/teams/Embedded-Artists/code/DM_USBHost/#9a462d032742 +http://developer.mbed.org/teams/Embedded-Artists/code/DM_USBHost/#f2d129436056
--- a/Display/BiosDisplay.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Display/BiosDisplay.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -64,11 +64,20 @@
DisplayError err = DisplayError_Ok;
if (!_initialized) {
do {
+
+ /*
+ * With mbed OS 5 the LCD controller is reset by default. This is set in
+ * system_LPC407x_8x_177x_8x.c "to prevent strange behavior when doing a
+ * partial reset (happens when debugging)". Taking the LCD controller
+ * out of reset below.
+ */
+ LPC_SC->RSTCON0 &= ~0x1;
+
if (BiosLoader::instance().params(&_bios, &_biosData) != DMBoard::Ok) {
err = DisplayError_ConfigError;
break;
}
-
+
err = (DisplayError)_bios->displayInit(_biosData);
if (err != DisplayError_Ok) {
break;
@@ -78,7 +87,7 @@
if (err != DisplayError_Ok) {
break;
}
-
+
_initialized = true;
} while(0);
}
@@ -92,6 +101,7 @@
err = init();
if (err == DisplayError_Ok) {
do {
+
err = (DisplayError)_bios->displayPowerUp(_biosData, framebuffer, (Resolution_t)res, rate);
if (err != DisplayError_Ok) {
--- a/Display/BiosTouch.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Display/BiosTouch.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -45,7 +45,7 @@
void changeTouchInterrupt(bool enable, touch_irq_trigger_t trigger);
TouchPanel::TouchError read(touch_coordinate_t* coord, int num);
void run();
- FunctionPointer* setListener(FunctionPointer* listener);
+ void setListener(Callback<void()> listener);
private:
Mail<touch_mail_t, NUM_MAILS> _mailbox;
Mutex _mutex;
@@ -54,7 +54,7 @@
bios_header_t* _bios;
void* _biosData;
int _points;
- FunctionPointer* _listener;
+ Callback<void()> _listener;
uint32_t _lostData;
uint32_t _dbgAdded;
uint32_t _dbgRemoved;
@@ -153,12 +153,9 @@
log->printf("got non-mail event: 0x%x\n", evt.status);
continue;
}
- _mutex.lock();
- FunctionPointer* fp = _listener;
- _mutex.unlock();
- if (fp != NULL) {
- fp->call();
+ if (_listener) {
+ _listener();
}
}
} else {
@@ -174,12 +171,9 @@
log->printf("got non-mail event: 0x%x\n", evt.status);
continue;
}
- _mutex.lock();
- FunctionPointer* fp = _listener;
- _mutex.unlock();
- if (fp != NULL) {
- fp->call();
+ if (_listener) {
+ _listener();
}
}
}
@@ -221,7 +215,7 @@
switch (trigger) {
case TOUCH_IRQ_RISING_EDGE:
if (enable) {
- _touchIRQ.rise(this, &TouchHandler::handleTouchInterrupt);
+ _touchIRQ.rise(callback(this, &TouchHandler::handleTouchInterrupt));
} else {
_touchIRQ.rise(NULL);
}
@@ -229,7 +223,7 @@
case TOUCH_IRQ_FALLING_EDGE:
if (enable) {
- _touchIRQ.fall(this, &TouchHandler::handleTouchInterrupt);
+ _touchIRQ.fall(callback(this, &TouchHandler::handleTouchInterrupt));
} else {
_touchIRQ.fall(NULL);
}
@@ -243,13 +237,11 @@
}
}
-FunctionPointer* TouchHandler::setListener(FunctionPointer* listener)
+void TouchHandler::setListener(Callback<void()> listener)
{
_mutex.lock();
- FunctionPointer* old = _listener;
_listener = listener;
_mutex.unlock();
- return old;
}
@@ -291,7 +283,8 @@
break;
}
- _handlerThread = new Thread(touchTask, _handler);
+ _handlerThread = new Thread();
+ _handlerThread->start(callback(touchTask, _handler));
_initialized = true;
} while(0);
@@ -383,10 +376,9 @@
return err;
}
-FunctionPointer* BiosTouch::setListener(FunctionPointer* listener)
+void BiosTouch::setListener(Callback<void()> listener)
{
if (_initialized) {
- return _handler->setListener(listener);
+ _handler->setListener(listener);
}
- return NULL;
}
--- a/Display/BiosTouch.h Wed Jun 10 09:54:15 2015 +0000
+++ b/Display/BiosTouch.h Wed Oct 23 06:59:29 2019 +0000
@@ -68,7 +68,7 @@
virtual TouchError calibrateStart();
virtual TouchError getNextCalibratePoint(uint16_t* x, uint16_t* y, bool* last=NULL);
virtual TouchError waitForCalibratePoint(bool* morePoints, uint32_t timeout);
- virtual FunctionPointer* setListener(FunctionPointer* listener);
+ virtual void setListener(Callback<void()> listener);
private:
--- a/Display/Display.h Wed Jun 10 09:54:15 2015 +0000
+++ b/Display/Display.h Wed Oct 23 06:59:29 2019 +0000
@@ -73,6 +73,11 @@
Resolution_24bit_rgb888 = Res_24bit_rgb888,
};
+ /**
+ * Initialize the display.
+ */
+ virtual DisplayError init() = 0;
+
/** Turns the display on with the specified framebuffer showing
*
* @param framebuffer the data to show
--- a/Display/TouchPanel.h Wed Jun 10 09:54:15 2015 +0000
+++ b/Display/TouchPanel.h Wed Oct 23 06:59:29 2019 +0000
@@ -116,8 +116,8 @@
* An error code on failure
*/
virtual TouchError waitForCalibratePoint(bool* morePoints, uint32_t timeout) = 0;
-
- virtual FunctionPointer* setListener(FunctionPointer* listener) = 0;
+
+ virtual void setListener(Callback<void()> listener) = 0;
};
#endif
--- a/EthernetInterface.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/mbed_official/code/EthernetInterface/#2fc406e2553f
--- a/FileSystems/MCIFileSystem.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/FileSystems/MCIFileSystem.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -345,22 +345,6 @@
Send CSR ACMD51 R1 x */
/**
- * @brief Possible SDMMC card state types
- */
-typedef enum {
- SDMMC_IDLE_ST = 0, /*!< Idle state */
- SDMMC_READY_ST, /*!< Ready state */
- SDMMC_IDENT_ST, /*!< Identification State */
- SDMMC_STBY_ST, /*!< standby state */
- SDMMC_TRAN_ST, /*!< transfer state */
- SDMMC_DATA_ST, /*!< Sending-data State */
- SDMMC_RCV_ST, /*!< Receive-data State */
- SDMMC_PRG_ST, /*!< Programming State */
- SDMMC_DIS_ST /*!< Disconnect State */
-} SDMMC_STATE_T;
-
-
-/**
* @brief SD/MMC commands, arguments and responses
* Standard SD/MMC commands (3.1) type argument response
*/
@@ -768,7 +752,7 @@
uint64_t MCIFileSystem::disk_sectors()
{
- debug_if(MCI_DBG, "mcifs:disk_sectors(), _Stat = %#x, returning %llu\n", _Stat, _sdCardInfo.blocknr);
+ debug_if(MCI_DBG, "mcifs:disk_sectors(), _Stat = %#x, returning %u\n", _Stat, _sdCardInfo.blocknr);
return _sdCardInfo.blocknr;
}
@@ -882,7 +866,7 @@
return Ret;
}
- Thread::wait(ACQUIRE_DELAY);
+ ThisThread::sleep_for(ACQUIRE_DELAY);
/* Send interface operation condiftion */
Ret = mci_SendIfCond();
@@ -915,7 +899,7 @@
uint32_t OCR;
/* Enter to Open Drain mode */
mci_PowerControl(PowerOn, SDC_PWR_OPENDRAIN);
- Thread::wait(ACQUIRE_DELAY);
+ ThisThread::sleep_for(ACQUIRE_DELAY);
Ret = mci_SendOpCond(&OCR);
if (Ret != SDC_RET_OK) {
return Ret;
@@ -1449,7 +1433,7 @@
{
ReturnCode Ret = SDC_RET_OK;
uint32_t status = 0;
- SDMMC_STATE_T state;
+ CardState state;
/* get current state of the card */
Ret = mci_GetStatus(rca, &status);
@@ -1459,7 +1443,7 @@
}
/* check card state in response */
- state = (SDMMC_STATE_T) R1_CURRENT_STATE(status);
+ state = (CardState) R1_CURRENT_STATE(status);
switch (state) {
case SDMMC_STBY_ST:
/* put card in 'Trans' state */
@@ -1469,7 +1453,7 @@
return Ret;
}
mci_GetStatus(rca, &status);
- if (((SDMMC_STATE_T) R1_CURRENT_STATE(status)) != SDMMC_TRAN_ST) {
+ if (((CardState) R1_CURRENT_STATE(status)) != SDMMC_TRAN_ST) {
return SDC_RET_ERR_STATE;
}
break;
--- a/FileSystems/QSPIFileSystem.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/FileSystems/QSPIFileSystem.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -1036,17 +1036,13 @@
virtual int close();
- virtual ssize_t write(const void *buffer, size_t length);
+ virtual ssize_t write(const void *buffer, size_t size);
- virtual ssize_t read(void *buffer, size_t length);
-
- virtual int isatty();
+ virtual ssize_t read(void *buffer, size_t size);
- virtual off_t lseek(off_t position, int whence);
+ virtual off_t seek(off_t offset, int whence = SEEK_SET);
- virtual int fsync();
-
- virtual off_t flen();
+ virtual off_t size();
protected:
@@ -1063,9 +1059,11 @@
virtual ~QSPIDirHandle();
- virtual int closedir();
- virtual struct dirent *readdir();
- virtual void rewinddir();
+ virtual ssize_t read(struct dirent *ent);
+ virtual int close();
+ virtual void seek(off_t offset);
+ virtual off_t tell();
+ virtual void rewind();
private:
QSPIDirHandle(const char* dirname);
@@ -1131,12 +1129,7 @@
return len;
}
-int QSPIFileHandle::isatty()
-{
- return 0;
-}
-
-off_t QSPIFileHandle::lseek(off_t position, int whence)
+off_t QSPIFileHandle::seek(off_t position, int whence)
{
switch (whence) {
case SEEK_SET:
@@ -1157,12 +1150,7 @@
return pos;
}
-int QSPIFileHandle::fsync()
-{
- return 0; // always synced
-}
-
-off_t QSPIFileHandle::flen()
+off_t QSPIFileHandle::size()
{
return fh.size;
}
@@ -1183,7 +1171,7 @@
}
}
cur_entry.d_name[HEADER_FNAME_STRLEN] = '\0';
- rewinddir();
+ rewind();
}
}
@@ -1212,11 +1200,18 @@
return d;
}
-int QSPIDirHandle::closedir() {
+int QSPIDirHandle::close() {
delete this;
return 0;
}
+void QSPIDirHandle::seek(off_t offset) {
+}
+
+off_t QSPIDirHandle::tell() {
+ return -1;
+}
+
int QSPIDirHandle::findFileWithPrefix(const char* prefix, int startTOCIdx, int maxTOCIdx) const
{
for (int i = startTOCIdx; i < maxTOCIdx; i++) {
@@ -1231,7 +1226,7 @@
}
-struct dirent *QSPIDirHandle::readdir() {
+ssize_t QSPIDirHandle::read(struct dirent *ent) {
if (nextTocIdx < NUM_BLOCKS) {
for (int i = nextTocIdx; i < NUM_BLOCKS; i++) {
int possible = findFileWithPrefix(dirname, i, NUM_BLOCKS);
@@ -1243,7 +1238,8 @@
// file is not in any sub folder so it is truly in the wanted dir
nextTocIdx = possible + 1;
strcpy(cur_entry.d_name, filename);
- return &cur_entry;
+ //return &cur_entry;
+ *ent = cur_entry;
}
// this is a file in a subfolder and should not be reported,
@@ -1266,7 +1262,8 @@
char* pSlash = strchr(cur_entry.d_name, '/');
// pSlash++; //with ++ the returned dir name is "mydir/" without ++ "mydir" is returned
*pSlash = '\0';
- return &cur_entry;
+ //return &cur_entry;
+ *ent = cur_entry;
}
}
}
@@ -1274,7 +1271,7 @@
return NULL;
}
-void QSPIDirHandle::rewinddir() {
+void QSPIDirHandle::rewind() {
nextTocIdx = 0;
}
--- a/FileSystems/USBMSD_RAMFS.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/FileSystems/USBMSD_RAMFS.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -16,12 +16,12 @@
#include "USBMSD_RAMFS.h"
-USBMSD_RAMFS::USBMSD_RAMFS(RAMFileSystem* ramfs, uint16_t vendor_id, uint16_t product_id, uint16_t product_release) :
- USBMSD(vendor_id, product_id, product_release)
+USBMSD_RAMFS::USBMSD_RAMFS(/*RAMFileSystem* ramfs*/HeapBlockDevice* ramfs, uint16_t vendor_id, uint16_t product_id, uint16_t product_release) :
+ USBMSD(ramfs, true, vendor_id, product_id, product_release)
{
- this->ramfs = ramfs;
+ //this->ramfs = ramfs;
}
-
+/*
int USBMSD_RAMFS::disk_read(uint8_t * data, uint64_t block, uint8_t count)
{
return ramfs->disk_read(data, block, count);
@@ -47,3 +47,4 @@
int USBMSD_RAMFS::disk_status() {
return ramfs->disk_status();
}
+*/
--- a/FileSystems/USBMSD_RAMFS.h Wed Jun 10 09:54:15 2015 +0000
+++ b/FileSystems/USBMSD_RAMFS.h Wed Oct 23 06:59:29 2019 +0000
@@ -36,10 +36,10 @@
* @param product_id Your product_id
* @param product_release Your preoduct_release
*/
- USBMSD_RAMFS(RAMFileSystem* ramfs, uint16_t vendor_id = 0x0703, uint16_t product_id = 0x0104, uint16_t product_release = 0x0001);
+ USBMSD_RAMFS(/*RAMFileSystem* ramfs*/HeapBlockDevice* ramfs, uint16_t vendor_id = 0x0703, uint16_t product_id = 0x0104, uint16_t product_release = 0x0001);
protected:
-
+#if 0
/*
* read one or more blocks on a storage chip
*
@@ -86,10 +86,11 @@
* @returns status: 0: OK, 1: disk not initialized, 2: no medium in the drive, 4: write protected
*/
virtual int disk_status();
+#endif
protected:
- RAMFileSystem* ramfs;
+ //RAMFileSystem* ramfs;
};
#endif
--- a/Memory/InternalEEPROM.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Memory/InternalEEPROM.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -123,20 +123,25 @@
void InternalEEPROM::init()
{
if (!_initialized) {
+
+ // The EEPROM peripheral is in ROM address space. Must allow
+ // writes to this address space.
+ mbed_mpu_manager_lock_rom_write();
+
// Bring EEPROM device out of power down mode
powerUp();
-
+
// Setup EEPROM timing to 375KHz based on PCLK rate
uint32_t clk = SystemCoreClock;
LPC_EEPROM->CLKDIV = SystemCoreClock / 375000 - 1;
-
+
// Setup wait states (ticks needed for 15ms, 55ms and 35ms)
uint32_t val;
val = ((((clk / 1000000) * 15) / 1000) + 1);
val |= (((((clk / 1000000) * 55) / 1000) + 1) << 8);
val |= (((((clk / 1000000) * 35) / 1000) + 1) << 16);
LPC_EEPROM->WSTATE = val;
-
+
_initialized = true;
}
}
--- a/Memory/sdram.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Memory/sdram.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -53,7 +53,7 @@
extern "C" unsigned __rt_heap_extend(unsigned size, void **block) {
static uint32_t lastReturnedBlock = 0;
-
+
if (okToUseSdramForHeap && !initialized) {
sdram_init();
}
@@ -563,7 +563,8 @@
#endif
LPC_EMC->DynamicControl = 0x00000183; /* Issue NOP command */
- wait(0.2); /* wait 200ms */
+ //wait(0.2); /* wait 200ms */
+ ThisThread::sleep_for(200);
LPC_EMC->DynamicControl = 0x00000103; /* Issue PALL command */
LPC_EMC->DynamicRefresh = 0x00000002; /* ( n * 16 ) -> 32 clock cycles */
for(i = 0; i < 0x80; i++); /* wait 128 AHB clock cycles */
--- a/Registry.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/Registry.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -156,30 +156,25 @@
do {
eeprom.init();
-
_data = (uint8_t*)malloc(eeprom.memorySize());
if (_data == NULL) {
err = MemoryError;
break;
}
-
+
uint32_t read = eeprom.read(0,_data,eeprom.memorySize());
if (read != eeprom.memorySize()) {
err = EEPROMReadError;
break;
}
-
eeprom.powerDown();
-
// decode the data
err = fromEEPROM();
if (err != Ok) {
free(_data);
_data = NULL;
}
-
} while(0);
-
_mutex.unlock();
return err;
}
--- a/RtosLog.cpp Wed Jun 10 09:54:15 2015 +0000
+++ b/RtosLog.cpp Wed Oct 23 06:59:29 2019 +0000
@@ -88,7 +88,8 @@
void RtosLog::init()
{
if (_thr == NULL) {
- _thr = new Thread(&RtosLog::logTask, this);
+ _thr = new Thread();
+ _thr->start(callback(&RtosLog::logTask, this));
}
}
@@ -97,7 +98,7 @@
// The pool has no "wait for free message" so we use a Sempahore
// to keep track of the number of free messages and, if needed,
// block the caller until a message is free
- _sem.wait();
+ _sem.acquire();
// Allocate a null terminated message. Will always succeed due to
// the semaphore above
@@ -136,8 +137,8 @@
// The pool has no "wait for free message" so we use a Sempahore
// to keep track of the number of free messages and, if needed,
// block the caller until a message is free
- int available = _sem.wait(0);
- if (available <= 0) {
+ bool available = _sem.try_acquire();
+ if (!available) {
// no free messages and it is not good to wait in an ISR so
// we discard the message
return 0;
--- a/USBDevice.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/mbed_official/code/USBDevice/#151ba33713ff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-os.lib Wed Oct 23 06:59:29 2019 +0000 @@ -0,0 +1,1 @@ +https://github.com/armmbed/mbed-os/#679d24833acf0a0b5b0d528576bb37c70863bc4e
--- a/mbed-rpc.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/mbed_official/code/mbed-rpc/#fece2d5e8d96
--- a/mbed-rtos.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/mbed_official/code/mbed-rtos/#ed4ff3bea947
--- a/mbed-src.lib Wed Jun 10 09:54:15 2015 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/mbed_official/code/mbed-src/#9f26fcd0c9ce
