This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Accessing External Memory Devices

Hey all,

I am using uVision 3 and a Phytec 2294 board.

I have an external memory device connected to the phytec expansion board. The external device is using /CS0, although from talking to phytec, I am not sure if that is quite right.

I have set up the external memory space in the project options like so:
Start: 0x80000000 Size: 0x4

When I define a variable like :

#define volatile var x __at 0x8000000

and step through my code, any write/read to that variable doesnt seem to take place at the right addresses.

Can anyone give a clue as to what I may have missed?
Thanks

Doug

Parents Reply Children
  • "When you say that creating data objects for external hardware registers is optional, how do you mean.

    How can I write to them or read from them, if I do not have them defined? I don't think I quite follow that line of reasoning?"


    I am making a distinction between using memory-allocating defining declarations for data objects (variables) and using the conventional direct access means for hardware registers.

    During program development you add code and data objects to your source code to implement your algorithms. You compile the code and link it to become an executable. For a simple system you supply base addresses where the linker should start allocating your code (.text), your data (.data, .bss), your stack, etc. and don't really care where the linker finally locates a particular data object -- just that you be able to access it. You let the linker allocate storage for your data objects and resolve their names to addresses.

    Hardware registers, on the other hand, are typically located at fixed addresses. No matter how you change your program, those hardware register addresses do not change. So it is quite conventional to not involve the linker and to use the kind of direct access means we've been talking about. For example, take a look at lpc22xx.h for your processor. Single registers have this form:

    /* External Memory Controller (EMC) */
    #define BCFG0          (*((volatile unsigned long *) 0xFFE00000))
    Here's a snippet from the Atmel header file for the ARM I am using to illustrate how registers are grouped by function into structures:
    typedef volatile unsigned int AT91_REG; // Hardware register definition
    
    // *****************************************************************************
    //      SOFTWARE API DEFINITION  FOR Memory Controller Interface
    // *****************************************************************************
    typedef struct _AT91S_MC {
    	AT91_REG	 MC_RCR; 	// MC Remap Control Register
    	AT91_REG	 MC_ASR; 	// MC Abort Status Register
    	AT91_REG	 MC_AASR; 	// MC Abort Address Status Register
    	AT91_REG	 Reserved0[1]; 	//
    	AT91_REG	 MC_PUIA[16]; 	// MC Protection Unit Area
    	AT91_REG	 MC_PUP; 	// MC Protection Unit Peripherals
    	AT91_REG	 MC_PUER; 	// MC Protection Unit Enable Register
    } AT91S_MC, *AT91PS_MC;
    
    // *****************************************************************************
    //      SOFTWARE API DEFINITION  FOR Real-Time Clock Alarm and
    //                                   Parallel Load Interface
    // *****************************************************************************
    typedef struct _AT91S_RTC {
    	AT91_REG	 RTC_CR; 	// Control Register
    	AT91_REG	 RTC_MR; 	// Mode Register
    	AT91_REG	 RTC_TIMR; 	// Time Register
    	AT91_REG	 RTC_CALR; 	// Calendar Register
    	AT91_REG	 RTC_TIMALR; 	// Time Alarm Register
    	AT91_REG	 RTC_CALALR; 	// Calendar Alarm Register
    	AT91_REG	 RTC_SR; 	// Status Register
    	AT91_REG	 RTC_SCCR; 	// Status Clear Command Register
    	AT91_REG	 RTC_IER; 	// Interrupt Enable Register
    	AT91_REG	 RTC_IDR; 	// Interrupt Disable Register
    	AT91_REG	 RTC_IMR; 	// Interrupt Mask Register
    	AT91_REG	 RTC_VER; 	// Valid Entry Register
    } AT91S_RTC, *AT91PS_RTC;
    
    #define AT91C_BASE_MC   ((AT91PS_MC) 	0xFFFFFF00) // (MC) Base Address
    #define AT91C_BASE_RTC  ((AT91PS_RTC) 	0xFFFFFE00) // (RTC) Base Address
    In both cases (your ARM and mine), memory-allocating defining declarations are not used and the linker would not be involved for hardware registers. Instead, macros are provided to directly access the registers. The same thing goes for new hardware you are having mapped into the 0x83000000 expansion area.

  • "The same thing goes for new hardware you are having mapped into the 0x83000000 expansion area."

    Unless, of course, that hardware is memory!

  • Dan,

    Well the new hardware is just a chip with hardware addresses.

    So I just want to assign register 1 at 0x83000000. Then perform reads and writes to it.

    Thats really it :)

    Doug

  • Simple then, once you have:

    #define MY_REG1 (*(volatile unsigned char *)0x83000000)
    Reading the register is:
    reg = MY_REG1;
    Writing the register:
    MY_REG1 = val;
    With debugger, simulators, or whatever in the way, you'll have to satisfy their requirements to get them to automagically configure your hardware and/or not fuss about accesses. If it is only your application running on the target hardware, after configuring the appropriate registers, there should be nothing to interfere with accessing your register.

  • Dan,

    In the Keil targget options, there is a spot to specify external memory, do I need to specify my ranges in there as well.

    I am unclear on how my code knows when I say go to 0x83000000, that it actually knows how to get to that address and read from it or write to it.

    Doug

  • "In the Keil targget options, there is a spot to specify external memory, do I need to specify my ranges in there as well."

    That, I think, is what you did to shut up the "*** error 65: access violation" messages. Don't think of it as adding a specification for external memory, but rather for an external memory-mapped device or peripheral. Look at lpc22xx.h and you'll see that there are memory-mapped peripherals in the 0xFF------ and 0xE0------ ranges. Presumably, the IDE already has those ranges covered, but you need to tell it not to fuss about accesses to your new memory-mapped I/O area. That is an artifact of using this type of toolchain. If you were doing it all with simple command-line compile/link/load tools, you wouldn't have to mess with it.

    "I am unclear on how my code knows when I say go to 0x83000000, that it actually knows how to get to that address and read from it or write to it."

    The way we're writing it here using the MY_REG1 macro (name it what you want), the compiler can't help but generate code to do it. It's going to generate code to load a register with 0x83000000 and to then do indirect loads and stores "through" that register. Check the disassembly and you'll see.

  • That is an artifact of using this type of toolchain. If you were doing it all with simple command-line compile/link/load tools, you wouldn't have to mess with it.

    Now there you're IMHO being a bit overly negative. Fact is, with such simpler tools, you just wouldn't have a simulator to be told about the existence (or not) of memory at given addresses on the simulated hardware.

    Calling the need to configure each part of the toolchain, which implies that having more elements in it will mean more configuration items to fill in, an artifact doesn't do it justice.

  • None of my comments were intended to be negative per se.

    There is a very clear distinction between configuration that has to be done to keep a toolchain happy and configuration that has to be done to make the hardware work.

    Nothing discussed so far has cleared up whether some toolchain settings actually have an effect on the hardware. I am wasting my time trying to help with what appears to be a very simple software/hardware integration issue when there are nuances to a toolchain that I can't see which are fogging up my glasses.

    When I am debugging software/hardware integration issues (e.g., a chip select), I remove as many sources of extraneous unknown variables as I can. But that's just me.

  • Dan,

    Actually, through some research, putting a value in that dialog does more harm than good.

    If I specify a range in there, I get :

    *** ERROR L107: ADDRESS SPACE OVERFLOW
        SPACE:   DATA
        SEGMENT: STACK
        LENGTH:  00000490H
    *** WARNING L23: UNRESOLVED EXTERNAL SYMBOLS
    *** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
        SYMBOL:  ?C?INIT
        ADDRESS: 0000010CH
    Program Size: data=1172 const=0 code=304
    Target not created
    

    If I leave it out, it compiles/links correctly.

    From the looks of my startup assembly file, it is running code from 0x00000000, im not sure if that is the right place to be starting from to do what I need to do or if it even matters.

    It doesnt appear that no matter what I do in the IDE, that it will even map the right addresses in the simulator.

    Doug

  • Are you only running this in the simulator and not the real target hardware?

  • Dan,

    I have taken a more simplistic approach for the time being.

    I have created a new projeect, with my definition of:

    #define MY_REG1 (*(volatile unsigned char *)0x83000000)

    In the dissasembler, when writing to the register I see

    0x00000608  B500      PUSH      {LR}
        30:   MY_REG1 = 1;
        31:
    

    My code base is currently set to 0x00000000



    Its got me stumped currently.

  • To answer original question, this is just in simulator

  • Nothing after that?

    Can you show more disassembly context?

  • 0x00000606 E7F1 B 0x000005EC
    27: int main (void) {
    28: unsigned int j; /* LED var */
    29:
    0x00000608 B500 PUSH {LR}
    30: MY_REG1 = 1;
    31:
    0x0000060A 2101 MOV R1,#0x01
    0x0000060C 4813 LDR R0,[PC,#0x004C]
    0x0000060E 7001 STRB R1,[R0,#0x00]
    32: IODIR1 = 0xFF0000; /* P1.16..23 defined as Outputs */
    33:
    0x00000610 4913 LDR R1,[PC,#0x004C]
    0x00000612 4814 LDR R0,[PC,#0x0050]
    0x00000614 6001 STR R1,[R0,#0x00]
    34: init_timer();

  • Much better. Thank you.

    0x00000606 E7F1 B 0x000005EC
    27: int main (void) {
    28: unsigned int j; /* LED var */
    29:
    0x00000608 B500 PUSH {LR}           ;Just saving some processor context there.

    30: MY_REG1 = 1;
    31:
    0x0000060A 2101 MOV R1,#0x01        ;Loading R1 with the value to be written.
    0x0000060C 4813 LDR R0,[PC,#0x004C] ;Loading R0 with the address to write to.
    0x0000060E 7001 STRB R1,[R0,#0x00]  ;Performing the byte write.
    What's probably throwing you here is at 0x0000060C with the load of R0 with 0x83000000 not being obvious. What is being loaded into R0 is the value the compiler stored at (PC + 0x4C). If you were to look further down in memory at 0x0000065C (or thereabouts), you should see 0x83000000 stored there. That's where the compiler stored our target address.