How to convert numeric constants to memory addresses in embedded development?
업데이트 시간: 2021-06-01 14:28:48
Recently in the development process of using Nordic's latest Bluetooth chip NRF52832, because doing some tests involves the operation of memory addresses, there is the usage of (*(volatile unsigned int *)0xE000EDFC) and then macro definition, this article will analyze this usage.
First look at the following code.
#define ARM_CM_DEMCR (*(volatile unsigned int *)0xE000EDFC)
#define ARM_CM_DWT_CTRL (*(volatile unsigned int *)0xE0001000)
#define ARM_CM_DWT_CYCCNT (*(volatile unsigned int *)0xE0001004)
The structure of this code analysis, because the nRF52832 is Cotex-M4 core, in the ARM processor, can only be recognized as a hexadecimal value, specifically data or address, it does not automatically distinguish.
Instead, use (unsigned int *)0xE000EDFC to force a conversion of this data, indicating that this value is an unsigned integer address pointer value and that the keyword volatile tells the compiler that what it points to is volatile and may be accidentally modified by hardware, etc.
*(volatile unsigned int *)0xFFE00000, on the other hand, gets the contents at the address pointed to by the pointer.
By enclosing the arguments in the #define macro in parentheses, the operation of ARM_CM_DEMCR in the user program is equivalent to a read or write operation at address 0xE000EDFC.
As mentioned above, in a 32-bit processor, to access a 32-bit memory address and then perform a read or write operation
tmp = ARM_CM_DEMCR; //read
ARM_CM_DEMCR = 0x55; //write
The volatile modifier is used because its value may change. Let's assume that we need to keep judging a memory data in a loop operation, for example waiting for the flag position of ARM_CM_DEMCR, because ARM_CM_DEMCR is mapped in SRAM space, in order to speed up the process, the compiler may compile To speed up the process, the compiler may compile code that reads ARM_CM_DEMCR into a register and then keeps determining the corresponding bit in the register without reading ARM_CM_DEMCR again.
In the actual project, for example, an interrupt event will change ARM_CM_DEMCR, but the corresponding bit of the register is not updated, which will cause a dead loop. If volatile is used as a modifier, then each time a variable is manipulated, it will be read from memory once.
Embedded system programming requires the programmer to be able to access a fixed memory address using the C language. Since it is an address, the amount representing the address should be a pointer type according to the C syntax rules.
For different computer architectures, devices may be port mapped or memory mapped. If the system architecture supports a separate IO address space and is port mapped, the actual control of the device must be done in assembly language, since C does not provide a true "port" concept.
3.The use of the volatile keyword
The meaning of volatile is to tell the compiler not to use optimizations on the variable when programming the source code, which may be done in C to optimize the value of the variable during some operations and cause the final result to be incorrect.
volatile also prevents the compiler from optimizing to remove certain statements, in arm if you need to write 1 clear break, for example, as follows
#define INTPAND *(volatile unsigned int *)0x560012300
INTPAND = INTPAND; // Clear interrupt
INTPAND = INTPAND; this operation, if there is no volatile modification, the compiler is likely to remove INTPAND = INTPAND;, which is equivalent to no such statement.
In embedded programming, when the address is io port, read and write this address is not cache it (cache only), for example, when writing this io port, if there is no volatile modification, the compiler will first write the value first to a buffer, to a certain point and then write to the io port, so that the data can not be written to the io port in a timely manner With the volatile modifier, the value is written directly to the io port, thus avoiding the delay in reading and writing to the io port.
4.Compiler optimization of the code
In order to improve efficiency, a cache is introduced to improve the speed of the CPU's execution, because the speed of accessing memory is not as fast as the execution speed of the CPU.
If the C compiler does not know that the variable will be modified by other external factors (OS, hardware or other threads), then it will optimize the variable (of course, some IDEs can set the optimization level) and the variable will be put into the cache cache during the execution of the CPU, thus achieving fast access to the variable.
In the case of register variables or data ports, the volatile modifier can also be used to avoid errors because the register variables themselves are also handled by the cache.
If the variable is changed by external factors, then the cpu cannot tell that the variable has been changed, so if the program uses the variable during execution, it will continue to use the variable in the cache (which has been changed) and needs to be updated in the memory address, so the variable cannot be put into the cache during execution.
The purpose of using volatile is to make access to volatile variables not cacheable to registers and need to be re-accessed each time they are used. This usage is very common and critical in embedded development and needs to be mastered.