This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

RTOS/TM4C123GH6PM: "remove()" works BUT rename() does not. How do I properly specify the arguments to rename?

Part Number: TM4C123GH6PM
Other Parts Discussed in Thread: SYSBIOS

Tool/software: TI-RTOS

Rename(old, new) used to work but I've done something and now it doesn't.

I've tried naked files and this...

Results...

ffconf.h looks like this...

/*---------------------------------------------------------------------------/
/  FatFs - FAT file system module configuration file  R0.08a (C)ChaN, 2010
/----------------------------------------------------------------------------/
/
/ CAUTION! Do not forget to make clean the project after any changes to
/ the configuration options.
/
/----------------------------------------------------------------------------*/
#ifndef _FFCONF
#define _FFCONF 8255    /* Revision ID */


/*---------------------------------------------------------------------------/
/ Function and Buffer Configurations
/----------------------------------------------------------------------------*/

#define _FS_TINY        0               /* 0:Normal or 1:Tiny  */
/* When _FS_TINY is set to 1, FatFs uses the sector buffer in the file system
/  object instead of the sector buffer in the individual file object for file
/  data transfer. This reduces memory consumption 512 bytes each file object. */


#define _FS_READONLY    0       /* 0:Read/Write or 1:Read only */
/* Setting _FS_READONLY to 1 defines read only configuration. This removes
/  writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename,
/  f_truncate and useless f_getfree. */


#define _FS_MINIMIZE    0       /* 0 to 3 !!was 0 */
/* The _FS_MINIMIZE option defines minimization level to remove some functions.
/
/   0: Full function.
/   1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename
/      are removed.
/   2: f_opendir and f_readdir are removed in addition to 1.
/   3: f_lseek is removed in addition to 2.
*/


#define _USE_STRFUNC    0       /* 0:Disable or 1/2:Enable */
/* To enable string functions, set _USE_STRFUNC to 1 or 2. */


#define _USE_MKFS       0               /* 0:Disable or 1:Enable  !!was 1 */
/* To enable f_mkfs function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */


#define _USE_FORWARD    0       /* 0:Disable or 1:Enable */
/* To enable f_forward function, set _USE_FORWARD to 1 and set _FS_TINY to 1. */


#define _USE_FASTSEEK   0       /* 0:Disable or 1:Enable */
/* To enable fast seek feature, set _USE_FASTSEEK to 1. */



/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/----------------------------------------------------------------------------*/

#define _CODE_PAGE      932
/* The _CODE_PAGE specifies the OEM code page to be used on the target system.
/  Incorrect setting of the code page can cause a file open failure.
/
/   932  - Japanese Shift-JIS (DBCS, OEM, Windows)
/   936  - Simplified Chinese GBK (DBCS, OEM, Windows)
/   949  - Korean (DBCS, OEM, Windows)
/   950  - Traditional Chinese Big5 (DBCS, OEM, Windows)
/   1250 - Central Europe (Windows)
/   1251 - Cyrillic (Windows)
/   1252 - Latin 1 (Windows)
/   1253 - Greek (Windows)
/   1254 - Turkish (Windows)
/   1255 - Hebrew (Windows)
/   1256 - Arabic (Windows)
/   1257 - Baltic (Windows)
/   1258 - Vietnam (OEM, Windows)
/   437  - U.S. (OEM)
/   720  - Arabic (OEM)
/   737  - Greek (OEM)
/   775  - Baltic (OEM)
/   850  - Multilingual Latin 1 (OEM)
/   858  - Multilingual Latin 1 + Euro (OEM)
/   852  - Latin 2 (OEM)
/   855  - Cyrillic (OEM)
/   866  - Russian (OEM)
/   857  - Turkish (OEM)
/   862  - Hebrew (OEM)
/   874  - Thai (OEM, Windows)
/       1    - ASCII only (Valid for non LFN cfg.)
*/


#define _USE_LFN        0               /* 0 to 3 */
#define _MAX_LFN        255             /* Maximum LFN length to handle (12 to 255) */
/* The _USE_LFN option switches the LFN support.
/
/   0: Disable LFN feature. _MAX_LFN and _LFN_UNICODE have no effect.
/   1: Enable LFN with static working buffer on the BSS. Always NOT reentrant.
/   2: Enable LFN with dynamic working buffer on the STACK.
/   3: Enable LFN with dynamic working buffer on the HEAP.
/
/  The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes. To enable LFN,
/  Unicode handling functions ff_convert() and ff_wtoupper() must be added
/  to the project. When enable to use heap, memory control functions
/  ff_memalloc() and ff_memfree() must be added to the project. */


#define _LFN_UNICODE    0       /* 0:ANSI/OEM or 1:Unicode */
/* To switch the character code set on FatFs API to Unicode,
/  enable LFN feature and set _LFN_UNICODE to 1. */


#define _FS_RPATH       0               /* 0 to 2 */
/* The _FS_RPATH option configures relative path feature.
/
/   0: Disable relative path feature and remove related functions.
/   1: Enable relative path. f_chdrive() and f_chdir() are available.
/   2: f_getcwd() is available in addition to 1.
/
/  Note that output of the f_readdir fnction is affected by this option. */



/*---------------------------------------------------------------------------/
/ Physical Drive Configurations
/----------------------------------------------------------------------------*/

#define _VOLUMES        1
/* Number of volumes (logical drives) to be used. !!was 4 */


#define _MAX_SS         512             /* 512, 1024, 2048 or 4096 */
/* Maximum sector size to be handled.
/  Always set 512 for memory card and hard disk but a larger value may be
/  required for floppy disk (512/1024) and optical disk (512/2048).
/  When _MAX_SS is larger than 512, GET_SECTOR_SIZE command must be implememted
/  to the disk_ioctl function. */


#define _MULTI_PARTITION        0       /* 0:Single partition or 1:Multiple partition */
/* When set to 0, each volume is bound to the same physical drive number and
/ it can mount only first primaly partition. When it is set to 1, each volume
/ is tied to the partitions listed in VolToPart[]. */


#define _USE_ERASE      0       /* 0:Disable or 1:Enable */
/* To enable sector erase feature, set _USE_ERASE to 1. */



/*---------------------------------------------------------------------------/
/ System Configurations
/----------------------------------------------------------------------------*/

#define _WORD_ACCESS    0       /* 0 or 1 */
/* Set 0 first and it is always compatible with all platforms. The _WORD_ACCESS
/  option defines which access method is used to the word data on the FAT volume.
/
/   0: Byte-by-byte access.
/   1: Word access. Do not choose this unless following condition is met.
/
/  When the byte order on the memory is big-endian or address miss-aligned word
/  access results incorrect behavior, the _WORD_ACCESS must be set to 0.
/  If it is not the case, the value can also be set to 1 to improve the
/  performance and code size. */


/* Include a header file here to define sync object types on the O/S */
/* #include <windows.h>, <ucos_ii.h.h>, <semphr.h> or ohters. */

#define _FS_REENTRANT   0       /* 0:Disable or 1:Enable */
#define _FS_TIMEOUT     1000    /* Timeout period in unit of time ticks */
#define _SYNC_t         void *  /* O/S dependent type of sync object. e.g. HANDLE, OS_EVENT*, ID and etc.. */

/* The _FS_REENTRANT option switches the reentrancy of the FatFs module.
/
/   0: Disable reentrancy. _SYNC_t and _FS_TIMEOUT have no effect.
/   1: Enable reentrancy. Also user provided synchronization handlers,
/      ff_req_grant, ff_rel_grant, ff_del_syncobj and ff_cre_syncobj
/      function must be added to the project. */


#define _FS_SHARE       0       /* 0:Disable or >=1:Enable */
/* To enable file shareing feature, set _FS_SHARE to 1 or greater. The value
   defines how many files can be opened simultaneously. */


#endif /* _FFCONFIG */

Your help always greatly appreciated.

Cheers,

Dave

  • Hi David,
    I'm not an expert in FatFS but have some questions:

    1. Are you using the native C library function rename() without TI-RTOS or are you making use of TI-RTOS FatFS?
    2. Is printB your own print function?
    3. You said rename() used to work? Do you remember what exactly did you change before it started to not work? Can you not revert back?
  • Hi Charles,
    1. I am using TI-RTOS FatFS.
    2. Yes, printB is my function to send formatted data both out UART1 to my Remote Controller and to the debug console screen.
    3. It's been a few years since I worked on the part of the program that used 'rename()' so I can't remember anything about it. In my current work I've just added a second pass to my tokenize() routine (which 'tokenizes' source code in my 'robot control language' interpreter in an effort to speed up interpretation). When 'running', the interpreter reads a line at a time from a file on SDcard and uses the FILE.pos (via ftell()) as a virtual 'program counter'. My 'language' supports 'while wend' loops and 'if endif' statements. The first iteration of the interpreter had to read and discard all the lines between the 'while' and the 'wend' in order to find the 'wend' when the truth condition of the 'while' became false. Very slow. The second pass of the tokenizer finds the 'wend' then adds its address to the 'tokenized' form of the 'while' instruction so that during interpretation that value is used to 'seek' the file pointer to the location in the file which is the line after the 'wend'. Much faster. The first pass of the tokenizer inputs fileName.SRC and outputs a tokenized file called fileName.BOB and sets a need2ndPass flag if a 'while' or 'if' instruction is encountered. The second pass takes fileName.BOB as input and outputs fileName.TXT. When the second pass completes it 'remove()s fileName.BOB and 'rename()s' fileName.TXT tofileName.BOB. And that's why I need 'rename' to work.

    The last thing I did that may have affected the system was: The interpreter is actually a 'multitasking' interpreter designed to read and interpret lines from up to ten open files in round robin fashion (implemented by means of ten 'virtual CPUs'). But the default state of the system limited _ftable to only ten entries three of which were used by stdin, stdout and stderr thus limiting me to only seven concurrently open files. With help from another TI expert I was able to increase _ftable size to 15 entries. To do this I had to change entries in 'stdio.h' and 'file.h' then rebuild the library. But during my preliminary floundering I may have wrongly changed settings in 'ffconf.h', 'stdio.h' and 'file.h' etc.

    I've just reviewed these files and everything looks right (as far as my limited understanding was able to determine) and rebuilt the library again. Sadly, the problem persists.

    What do you think?
    Thanks,
    Dave
  • Hi Dave,
    I will forward your post to our TI-RTOS experts for some guidance. Is the 'robot control language' interpreter running on your PC? I'm trying to understand what is wrong on the MCU side that is not working. I'm just not knowledgeable enough here to decipher if yourdescribed problem is TI-RTOS related or not. If you can clarify that will help our TI-RTOS experts in diagnosing the problem.
  • Hi Charles,

    The tokenizer, interpreter, RTOS and everything  runs in about 90K (so far) in the TM4C123GH6PM.

    (It's all really quite simple. The 'language' is roughly modeled after a simple 'assembly language' (like for a 6502 or 6811) in which every instruction begins with  an 'opcode' verb. All the tokenizer does is look up the first word in each line and replace it with the index number (stringified) of the list in which the word is found. During interpretation each line read from a file is parsed into an array of tokens and the first token, the opcode, is converted by atoi() and fed into a switch statement. Each 'case' in the switch calls one subroutine which handles the intent of the instruction using the rest of the tokens as 'operands'. Interpretation of each file is managed via a 'virtual cpu' struct. Here's a clarifying picture...

    Cheers,

    Dave

  • Hi Dave,

    1. What version SDK is this project based on?
    2. Do you know what value rename() is returning? Have you tried stepping into that code to observe what happens?
    3. Does this issue occur consistently, or is it intermittent?
    4. You mentioned you had to rebuild a library to work with more files at once. Have you tried using the original library to see if that causes the issue to go away? That would help us start to isolate the issue.

    Thanks,

    Sean
  • Hello Sean,

    About:

    1. I don't know. I think it's not based on an SDK.

    2. rename() is returning -1.

    3 The issue is absolutely consistent.

    4 In 'stdio.h' changed the _NFILE entry value from 10 to 15.

    'file.h' changed the _NSTREAM entry value from 10 to 15.

    I was advised to delete rtsv7M4_T_le_v4SPD16_eabi.lib to force a rebuild of it which would incorporate my changes, and so I did and it worked. _ftable now has 15 entries.

    RemoveFile() works fine, I stepped thru it and saw where it returned 0.

    I stepped thru renameFile(), couldn't make sense of most of the assembly (not familiar with ARM). I did see where it returned -1 somewhat after finddevice() returned null (as shown below).

    Are there some files I should send you?

    Thanks for your help.

    Cheers,

    Dave

    p.s. Here is how my debug console looks after the robot boots and I tell it to tokenize file k.src

    (things line up better on the debug screen than it appears here)...

    Hello  World

    Opened SYSTEM.TXT r
    Closed SYSTEM.TXT
    System restored

    Clock started: Millisec

    !ReportBump b 7 1
    !ReportUnit unit 1_5E98C1
    !ReportVolt batt 11.9
    !ReportTime setTime 3760768312

    Clock started: PID
    Clock started: Location
    Clock started: AtoD
    Clock started: ArmServos
    Clock started: Scanner

    Ok: tokenize k
    Tokenizing K.SRC
    PASS 1 --------------------------------------
    Opened K.SRC r
    Opened K.BOB w
        OP OPERANDS
        74 on
        43 x 10
        52
        53 Secs 0 Msecs 0
        46 @2Seek Test\n
        55 ....... 0 x <
        54 ....... x 5 <=
        46 @%d@%d@6 x = %d\n,x,x,x
        81
        42 x
        56
        46 @2 x = %d\n,x
        46 @3This test took\n
        46 @4%d.%d\n, Secs , Msecs
        46 @5seconds.\n
        35
    Closed K.SRC
    Closed K.BOB

    PASS 2 --------------------------------------
    Opened K.BOB r
    Opened TMP.BOB w
       POSITION OP OPERANDS
          0 74 on
          6 43 x 10
         14 52
         17 53 Secs 0 Msecs 0
         35 46 @2Seek Test\n
         52 55 125     0 x <
         69 54 117     x 5 <=
         87 46 @%d@%d@6 x = %d\n,x,x,x
        114 81
        117 42 x
        122 56
        125 46 @2 x = %d\n,x
        142 46 @3This test took\n
        164 46 @4%d.%d\n, Secs , Msecs
        191 46 @5seconds.\n
        207 35
    Closed K.BOB
    Closed TMP.BOB
    Removed K.BOB
    NOT RENAMED:TMP.BOB to K.BOB
    retval = -1
    Rename: Domain error
    Tokenized K.SRC

    Ok: list k.src C
    Opened K.SRC r
        // Seek Test
        trace on
        var x 10
        cls
        set Secs 0 Msecs 0
        ? @2Seek Test\n
        while 0 < x
            if x <= 5
                ? @%d@%d@6 x = %d\n,x,x,x
            endif
            -- x
        wend
        ? @2 x = %d\n,x
        ? @3This test took\n
        ? @4%d.%d\n, Secs , Msecs
        ? @5seconds.\n
        end
    Closed K.SRC
    Done Listing

    Ok:

  • Hi Dave,

    Maybe you could send me the tests that are being run as well as the files containing removeFile() etc.
    Are those previous stdio calls checked for errors? ie are fopen, fclose, and remove all returning 0?

    A few things I'd try:
    - If you look at oldbuf and newbuf in memory, is there old stack data still in the buffers? Maybe that is causing a string compare issue?
    - What happens if you try to fopen TMP.BOB right before you try to rename it? Does that operation succeed?
    - What happens if you try to rename TMP.BOB to something that isn't K.BOB? Maybe there's a filename contention there for some reason?

    Thanks,

    Sean
  • Hi Dave,

    Any updates on this? Were you able to pin down the problem?

    Thanks,

    Sean
  • Hi Sean,
    I thought I'd let rename() swim around in the unconscious for a while as I worked out other problems in the system. Now everything works as it should (I believe everything ho ho , more testing in order natch). To enable this activity I wrote a 'work around' in which I simply copied the TMP.BOB file over the K.BOB file thereby achieving the same result as rename().

    So just now...
    I stepped thru rename() in assembler on one screen and observed lowlevel.c on another screen trying to find the condition causing rename to fail. Here are my notes copypasted from both screens...
    ___________________________________________________________________________________

    void renameFile(char *old, char *new){ <--- my rename function
    char oldbuf[20];
    char newbuf[20];
    sprintf(oldbuf, "fat:0:%s", old);
    sprintf(newbuf, "fat:0:%s", new);
    if(rename(oldbuf, newbuf) isNot 0){... <--- disassembly starts

    rename():
    getdevice():
    finddevice():
    return NULL;
    dev = finddevice(devname); <--- dev is 'fat'

    return stdevice; /* the "standard" device - host I/O */
    old_dev = getdevice(&old_name); <--- old_dev is 'fat'
    new_dev = getdevice(&new_name); <--- new_dev is 'fat'
    if (old_dev != new_dev)
    result = (*(old_dev->RENAME)) (old_name, new_name);

    Can't find a source file at "/db/vtree/library/trees/zumaprod/zumaprod-f33/tirtos_tivac_2_12_01_33/products/bios_6_41_04_54/packages/ti/sysbios/fatfs/ffcio.c"
    Locate the file or edit the source lookup path to include its location.
    ffcio_rename():
    00013186: B508 push {r3, lr}
    00013188: F7F5FEF8 bl #0x8f7c

    f_rename():
    00008f7c: B570 push {r4, r5, r6, lr}
    .
    .
    .
    00008fc8: F005F938 bl #0xe23c
    00008fcc: 1C04 adds r4, r0, #0
    00008fce: D059 beq $C$L173
    00008fd0: 2C04 cmp r4, #4
    00008fd2: D15A bne $C$L175 <--- r4 is 6, not 0
    $C$L175:
    0000908a: 9801 ldr r0, [sp, #4]
    0000908c: 4621 mov r1, r4
    0000908e: F009FEA5 bl #0x12ddc
    00009092: 4620 mov r0, r4
    00009094: B018 add sp, #0x60
    00009096: BD70 pop {r4, r5, r6, pc}

    0001318c: 2800 cmp r0, #0 <--- r0 is 6
    0001318e: BF14 ite ne
    00013190: F04F30FF mov.w r0, #-1 <--- this executes
    00013194: 2000 movs r0, #0
    00013196: BD08 pop {r3, pc}
    result = (*(old_dev->RENAME)) (old_name, new_name); <--- result is -1
    00002bb2: 4628 mov r0, r5
    00002bb4: BD3E pop {r1, r2, r3, r4, r5, pc}

    result = (*(old_dev->RENAME)) (old_name, new_name); <--- result is -1
    __TI_resource_unlock(__TI_LOCK_DEVICE_TBL);

    0000d95a: B928 cbnz r0, #0xd968 <--- r0 is -1
    104 printB("RENAME %s \rto %s \rFAILED\n", oldbuf, newbuf); <--- #0xd968 is here, FAIL
    ___________________________________________________________________________________

    I restored all the values in 'stdio'h and 'file.h' that I wrongly messed with while trying to increase _ftable from 10 to 15 entries, deleted rtsv7M4_T_le_v4SPD16_eabi.lib and 'cleaned' my project forcing rebuild but rename() still fails.

    Do you have a list of 'most likely causes of rename() failure' I could investigate?
    Thanks very much,
    Dave
    p.s.
    I'm very happy with my system, as a speed test: able to plug 10 files into 10 virtual CPUs and have them all run 'concurrently' reading from 10 different files interpreting
    for a = 0 to 10000
    n = ((b x c) + (d + e) )SQU
    next
    at over 500 iterations per second on each cpu.

  • Hi Dave,

    Glad to hear you have a work around. You can give CCS the file it's looking for to see which function is failing in ff.c. When it says 'Can't find a source file at "/db/vtree/library/trees/zumaprod/zumaprod-f33/tirtos_tivac_2_12_01_33/....."
    Locate the file or edit the source lookup path to include its location.' you can give it ff.c from your sdk (which you can redownload here if need be: software-dl.ti.com/.../index.html )

    Both ff.c and ffcio.c should be in the path: \ti\tirtos_tivac_2_12_01_33\products\bios_6_41_04_54\packages\ti\sysbios\fatfs

    This should give you a better idea of where FatFs fails. Unfortunately I was unable to reproduce the issue with rename() so unless you can trace the problem from your side, I would recommend using your work around.

    Sean

  • Hi Sean,

    OK, got to single step thru ff.c...

    In ff.c...

    FRESULT f_rename (
            const TCHAR *path_old,  /* Pointer to the old name */
            const TCHAR *path_new   /* Pointer to the new name */
    )
    {
            FRESULT res;
            DIR djo, djn;
            BYTE buf[21], *dir;
            DWORD dw;
            DEF_NAMEBUF;


            res = chk_mounted(&path_old, &djo.fs, 1);
            if (res == FR_OK) {
                    djn.fs = djo.fs;
                    INIT_BUF(djo);
                    res = follow_path(&djo, path_old);   /* Check old object */
                    if (_FS_RPATH && res == FR_OK && (djo.fn[NS] & NS_DOT))
                            res = FR_INVALID_NAME;
    #if _FS_SHARE
                    if (res == FR_OK) res = chk_lock(&djo, 2);
    #endif
                    if (res == FR_OK) {                /* Old object is found */
                            if (!djo.dir) {            /* Is root dir? */
                                    res = FR_NO_FILE;
                            } else {
                                    mem_cpy(buf, djo.dir+DIR_Attr, 21); /* Save the object...
                                    mem_cpy(&djn, &djo, sizeof(DIR));   /* Check new object */
                                    res = follow_path(&djn, path_new); <----This line is returning 6 !

    6 is:
    Macro: int ENXIO

    “No such device or address.” The system tried to use the device represented by a file you specified, and it couldn’t find the device. This can mean that the device file was installed incorrectly, or that the physical device is missing or not correctly attached to the computer".

    Again, my function that calls rename():

    // **************************************************************** TM4C123GH6PM
    void renameFile(char *old, char *new){
    char oldbuf[20];
    char newbuf[20];

        sprintf(oldbuf, "fat:0:%s", old);
        sprintf(newbuf, "fat:0:%s", new);
        if(rename(oldbuf, newbuf) isNot 0){
            printB("RENAME FAILED \r%s to %s\n", oldbuf, newbuf);
            perror("RENAME");
    //        return false;
        }
        else
            printB("Renamed \r%s to \r%s\n", oldbuf, newbuf);
    //    return true;
    }

    Which passes "fat:0:TMP.BOB" and "fat:0:K.BOB" to rename().

    Does this help?

    I really don't want to stay with the work around.

    Any ideas?

    Cheers,

    Dave

     

  • Hi Dave,

    I'm not sure how much we can help with this since we cannot reproduce it and it sounds like something you've changed is the reason why it is no longer working. Can you go back to a working setup and then roll the changes in to determine what caused the issue?

    Todd
  • Hi Sean,
    Would updating CCS wipe out my changes?
    I'm worried my project (4 years in development) might be affected.
    Is there a chance of that?
    Thanks,
    Dave

  • David,

    It sounds like you better stick with your work-around. Unfortunately I don't think there is much more we can help you with on this matter, so I'm marking the thread as "TI Thinks Resolved".

    Todd
  • Hi Sean,

    I suspicioned this might turn out to be embarrassing and I was right.

    I can mark this as problem solved.

    Here's what happened:

    Because I have only the one SDcard drive: In file ffconf.h I changed

    #define _VOLUMES        4
    /* Number of volumes (logical drives) to be used.  */

    to

    #define _VOLUMES        1
    /* Number of volumes (logical drives) to be used. */

    in hopes that this would perhaps save some RAM somewhere.

    All of my disk access commands (openFile() closeFile() removeFile() etc.) prepended the string "fat:0:" to the file name.

    Specifying the drive number was correct when there were more than 1 logical drives specified but changing it to only 1 seems to have introduced the error.

    Now when I prepend only "fat:" to the file name all file access commands including rename() work as expected.

    The odd thing was that all the commands except rename() worked properly when the drive number was included in the file specification string.

    It was only by single stepping that I discovered starting at line 3194 in ff.c f_rename

    mem_cpy(&djn, &djo, sizeof(DIR));

    was copying the '0:' and

    res = follow_path(&djn, path_new);

    was finding the colon after the zero to be an 'illegal' character that I twigged to the problem.

    There you go.

    Thanks again for your help.

    Cheers,

    Dave

  • Hi Dave,

    Glad you worked it out! Thanks for reporting back on the solution, it's nice to have resolution. Have a good day!

    Sean