Chapter 4 Arrays, Time, and Functional Debugging

Jonathan Valvano and Ramesh Yerraballi

 

In this chapter, we will illustrate a formal method for testing. Because embedded systems are deployed in safety-critical systems, we need to be rigorous in our methods that evaluate if the deployed system performs its tasks as required. Embedded systems not only need to arrive at the correct answer, they need to arrive at it at the correct time. We will introduce a simple hardware counter that we can use to measure time. The testing method we will develop in this chapter will be to create a data logger to store when and what our system is doing.

 

Table of Contents:

Video 4.0. Introduction to Arrays and Functional Debugging

4.1. Arm Architecture Procedure Call Standard (AAPCS)

Arm Architecture Procedure Call Standard (AAPCS) allows your code to work with others. The procedures relevant to this course include:


It is not officially an AAPCS rule, but we must save the LR if one function calls another function.


4.2. Debugging

Every programmer is faced with the need to debug and verify the correctness of his or her software. There are two important aspects of debugging: control and observability. Control allows you to run small pieces of code over and over with know inputs. Observability allows the programmer to visualize the inputs and outputs. A debugging instrument is hardware or software used for the purpose of debugging. In this class, we will study hardware-level probes like the logic analyzer, oscilloscope, and Joint Test Action Group (JTAG standardized as the IEEE 1149.1) interface; software-level tools like simulators, monitors, and profilers; and manual tools like inspection and print statements. Nonintrusiveness is the characteristic or quality of a debugger that allows the software/hardware system to operate normally as if the debugger did not exist. Intrusiveness is used as a measure of the degree of perturbation caused in system performance by the debugging instrument itself. For example, a print statement added to your source code is very intrusive because it significantly affects the real-time interaction of the hardware and software. It is important to quantify the intrusiveness of an instrument. Let t be the average time it takes to run the software code comprising the debugging instrument. This time t is how much less time the system has, in order to perform its regular duties. Let Δt be the average time between executions of the instrument. A quantitative measure of intrusiveness is t/Δt, which is the fraction of the time consumed by the process of debugging itself. A debugging instrument is classified as minimally intrusive if it has a negligible effect on the system being debugged. In other words, if t/Δt is so small that the debugging activities have a finite but inconsequential effect on the system behavior, we classify it as minimally intrusive. In a real microcomputer system, breakpoints and single-stepping are intrusive, because the real hardware continues to change while the software has stopped. When a program interacts with real-time events, the performance can be significantly altered when using intrusive debugging tools. On the other hand, we will learn later in this chapter that dumps, dumps with filter, and monitors (e.g., which output strategic information on LEDs or an OLED display) are much less intrusive. A logic analyzer that passively monitors the activity of the software is completely nonintrusive. Interestingly, breakpoints and single-stepping on a mixed hardware/software simulator are often nonintrusive, because the simulated hardware and the simulated software are affected together.

For example, the heartbeat code GPIO_PORTF_DATA_R ^= 0x02; requires only 6 bus cycles to execute. If the heartbeat runs every 1ms, and the bus clock is 80 MHz, then is equal to 6/80000. Normally, if this ratio is less than 1/1000 we classify it minimally intrusive.

: What does it mean for a debugging instrument to be minimally intrusive? Give both a general answer and a specific criterion.


Stabilization is process of fixing the inputs so that the system can be run over and over again yielding repeatable outputs.

Black-box testing is simply observing the inputs and outputs without looking inside. Black-box testing has an important place in debugging a module for its functionality. On the other hand, white-box testing allows you to control and observe the internal workings of a system. A common mistake made by new engineers is to just perform black box testing. Effective debugging uses both. One must always start with black-box testing by subjecting a hardware or software module to appropriate test-cases. Once we document the failed test-cases, we can use them to aid us in effectively performing the task of white-box testing.

A print statement is a common example of a debugging instrument. Using the editor, one adds print statements to the code that either verify proper operation or illustrate the programming errors. If we test a system, then remove the instruments, the system may actually stop working, because of the importance of timing in embedded systems. If we leave debugging instruments in the final product, we can use the instruments to test systems on the production line, or test systems returned for repair. On the other hand, sometimes we wish to provide a mechanism to reliably and efficiently remove all instruments when the debugging is done. Consider the following mechanisms as you develop your own unique debugging style.

Place all instruments in a unique column, so you can easily distinguish instruments from regular programs.

Define all debugging instruments as functions that all have a specific pattern in their names. In this way, the find/replace mechanism of the editor can be used to find all the calls to the instruments.

Define the instruments so that they test a run time global flag. When this flag is turned off, the instruments perform no function. Notice that this method leaves a permanent copy of the debugging code in the final system, causing it to suffer a runtime overhead, but the debugging code can be activated dynamically without recompiling. Many commercial software applications utilize this method because it simplifies “on-site” customer support.

Use conditional compilation (or conditional assembly) to turn on and off the instruments when the software is compiled. When the assembler or compiler supports this feature, it can provide both performance and effectiveness.

 


: Consider the difference between a runtime flag that activates a debugging command versus an assembly/compile-time flag. In both cases it is easy to activate/deactivate the debugging statements. For each method, list one factor for which that method is superior to the other.

: What is the advantage of leaving debugging instruments in a final delivered product?   

Observation: There are two important components of debugging: having control over events and being able to see what is happening. Remember: control and observability!

Common Error: The most common debugging mistake new programmers make is to simply observe the overall inputs and outputs system without looking inside the device. Then they go to their professor and say, “My program gives incorrect output. Do you know why?”

 

4.3. SysTick Timer

Before we describe the process of instrumentation, we will discus a feature that exists on all Cortex-M microcontrollers, a timer, called SysTick. Therefore, the use of SysTick in designing your system will assure you that your system will easily port to other Cortex-M microcontrollers. SysTick is a simple counter that we can use to create time delays and generate periodic interrupts. Table 4.1 shows the register definitions for SysTick. The basis of SysTick is a 24-bit down counter that runs at the bus clock frequency. There are four steps involved in the initialization of the SysTick timer. First, we clear the ENABLE bit to turn off SysTick during initialization. Second, we set the RELOAD register. Third, we write to the NVIC_ST_CURRENT_R value to clear the counter. Lastly, we write the desired mode to the control register, NVIC_ST_CTRL_R. The mode involves the CLK_SRC and the INTEN bits. We set the CLK_SRC bit specifying the core clock will be used. We will set CLK_SRC=1, so the counter runs off the system clock. Later, we will set INTEN to enable interrupts, but in this first example we clear INTEN so interrupts will not be requested. We need to set the ENABLE bit so the counter will run. Once the initialization is complete, the timer starts to count down, i.e., CURRENT is decremented once every clock tick.
When the CURRENT value counts down from 1 to 0, the COUNT flag is set. On the next clock, the CURRENT is loaded with the RELOAD value. In this way, the SysTick counter (CURRENT) is continuously decrementing. If the RELOAD value is n, then the SysTick counter operates at modulo n+1 (…n, n-1, n-2 … 1, 0, n, n-1, …). In other words, it rolls over every n+1 counts. In this chapter we set RELOAD to 0x00FFFFFF, so the CURRENT value is a simple indicator of what count is now. Noting what the count was at some point and then what it is now, allows us to calculate the time that has elapsed

 

Address

31-24

23-17

16

15-3

2

1

0

Name

$E000E010

0

0

COUNT

0

CLK_SRC

INTEN

ENABLE

NVIC_ST_CTRL_R

$E000E014

0

24-bit RELOAD value

NVIC_ST_RELOAD_R

$E000E018

0

24-bit CURRENT value of SysTick counter

NVIC_ST_CURRENT_R

Table 4.1. SysTick registers.

 

If we activate the PLL to run the microcontroller at 80 MHz, then the SysTick counter decrements every 12.5 ns. In general, if the period of the core bus clock is t, then the COUNT flag will be set every (n+1)t. Note that, reading the NVIC_ST_CTRL_R control register will return the COUNT flag in bit 16. The act of reading this register when the COUNT flag is set will automatically clear it (post read). Also, writing any value to the NVIC_ST_CURRENT_R  register will reset the counter to zero and clear the COUNT flag. Program 4.1 initializes the SysTick. To determine the time, one simply reads the NVIC_ST_CURRENT_R register.

 

#define NVIC_ST_CTRL_R      (*((volatile unsigned long *)0xE000E010))

#define NVIC_ST_RELOAD_R    (*((volatile unsigned long *)0xE000E014))

#define NVIC_ST_CURRENT_R   (*((volatile unsigned long *)0xE000E018))

void SysTick_Init(void){

  NVIC_ST_CTRL_R = 0;              // 1) disable SysTick during setup

  NVIC_ST_RELOAD_R = 0x00FFFFFF;   // 2) maximum reload value

  NVIC_ST_CURRENT_R = 0;           // 3) any write to current clears it

  NVIC_ST_CTRL_R = 0x00000005;     // 4) enable SysTick with core clock

}

Program 4.1. Initialization of SysTick.

Video 4.1. Working of the Systick Timer

Program 4.2 shows how to measure the elapsed time between calls to a function. Assume the system calls the function Action() over and over. The variable Now is the time (in 12.5ns units) when the function has been called. The variable Last is the time (also in 12.5ns units) when the function was called previously. To measure elapsed time, we perform a time subtraction. Since the SysTick counts down we subtract Last-Now. Since the time is only 24 bits and the software variables are 32 bits we “and” with 0x00FFFFFF to create a 24-bit difference.

 

uint32_t Now;      // 24-bit time at this call (12.5ns)

uint32_t Last;     // 24-bit time at previous call (12.5ns)

uint32_t Elapsed;  // 24-bit time between calls (12.5ns)

void Action(void){      // function under test

  Now = NVIC_ST_CURRENT_R;         // what time is it now?

  Elapsed = (Last-Now)&0x00FFFFFF; // 24-bit difference

  Last = Now;                      // set up for next

  ...                      

}

Program 4.2. Use of SysTick to measure elapsed time.

The first measurement will be wrong because there is no previous execution from which to measure. The system will be accurate as long as the elapsed time is less than 0.209 second. More precisely, as long as the elapsed time is less than 224*12.5ns. This is similar to the problem of using an analog clock to measure elapsed time. For example you notice the clock says 10:00 when you go to sleep, and you notice it says 7:00 when you wake up. As long as you are sure you slept less than 12 hours, you are confident you slept for 9 hours.

Our TM4C123 microcontroller has some 32-bit and some 64-bit timers, but we will use SysTick because it is much simpler to configure. We just have to be aware that we are limited to 24 bits.

. If the bus clock to 80 MHz (12.5ns) and we were to use a 32-bit timer, what is the longest elapsed time we could measure?

4.4. Arrays in Assembly

Video 4.2. Assembly language access to arrays

In developing our instrument we will need a place to put data. One of the simplest and fastest places to store data is in RAM memory. The TM4C123 has 32 kibibytes of RAM, and we can use it to store temporary data. If the information is a constant, and we know its values at compile time, we can place the data in ROM. The TM4C123 has 256 kibibytes of flash ROM, and we can use it to store constant data. In assembly, we define a variable array in RAM using the AREA Data pseudo-op. We define a constant array in ROM using the AREA |.text| pseudo-op
      AREA  Data
A     SPACE 400
      AREA  |.text|
Prime DCW 1,2,3,5,7,11,13

In C, the compiler will automatically put variable arrays in RAM and constant arrays in ROM.
uint32_t A[100];
const uint16_t Prime[] = {1,2,3,5,7,11,13};


The following functions will add up the 7 values from the constant array. If the address of the array is base, the number of bytes in each element is n, and the index is i, then the address of the ith element is base+n*i. In the following example, each value is a 16-bit unsigned integer, or 2 bytes. In assembly, we have
j   RN 1 ;j is 2*i
sum RN 0
Sum7Primes
    LDR R3,=Prime
    MOV sum,#0 ;sum = 0
    MOV j,#0   ;ofs = 0
lp  LDRH R2,[R3,j] ;Prime[i]
    ADD sum,sum,R2 ; sum
    ADD j,j,#2 ; ofs += 2
    CMP j,#14  ; end?
    BLO lp
    BX LR

In C, we have
uint32_t Sum7Primes(void){
  uint32_t sum = 0;
  uint32_t i = 0;
  do{
    sum += Prime[i];
    i++;
  }while(i<7);
  return sum;
}


The previous example hard-coded the address and size of the array. To make it more general, we will pass in the address of the array as a pointer, and pass the size of the array as an integer. In this example each element is 32-bits, or 4 bytes, In assembly, the parameter in R0 is the base address of the array. The size of the array is passed by value in R1
p   RN 0
n   RN 1
sum RN 2
x   RN 3
Sum MOV sum,#0 ;sum = 0
lp  LDR x,[p] ;*p
    ADD sum,sum,x ;sum +=x
    ADD p,p,#4 ;p++
    SUBS n,n,#1
    BNE lp
    MOV R0,sum
    BX LR

In C, we have
uint32_t Sum(uint32_t *p, uint32_t n){
  uint32_t sum,x;
  sum = 0;
  for(; n; n--){
    x = *p;
    sum += x;
    p++;
  }
  return sum;
}


To invoke a function with pointer parameter we use call by reference, which means we pass the address. In assembly, we have
     AREA data
SIZE EQU 100
y    SPACE 4
Buf  SPACE SIZE*4
     AREA |.text|
main LDR R0,=Buf
     MOV R1,#SIZE
     BL  Sum
     LDR R1,=y
     STR R0,[R1]
Loop B Loop


In C, we have
#define SIZE 100
int32_t Buf[SIZE];
int32_t y;
int main(void){
  y = Sum(Buf,SIZE);
  while(1){
  }
}


 

4.5. Functional debugging

Video 4.3. Testing and Debugging - Intrusiveness

4.5.1. Stabilization

Functional debugging involves the verification of input/output parameters. Functional debugging is a static process where inputs are supplied, the system is run, and the outputs are compared against the expected results. Four methods of functional debugging are presented in this section. There are two important aspects of debugging: control and observability. The first step of debugging is to stabilize the system. In the debugging context, we stabilize the system by creating a test routine that fixes (or stabilizes) all the inputs. In this way, we can reproduce the exact inputs over and over again. Stabilization is an effective approach to debugging because we can control exactly what software is being executed. Once stabilized, if we modify the program, we are sure that the change in our outputs is a function of the modification we made in our software and not due to a change in the input parameters. When a system has a small number of possible inputs (e.g., less than a million), it makes sense to test them all. When the number of possible inputs is large we need to choose a set of inputs. There are many ways to make this choice. We can select values:

Near the extremes and in the middle

Most typical of how our clients will properly use the system

Most typical of how our clients will improperly attempt to use the system

That differ by one

You know your system will find difficult

Using a random number generator

 

To stabilize the system we define a fixed set of inputs to test, run the system on these inputs, and record the outputs. Debugging is a process of finding patterns in the differences between recorded behavior and expected results. The advantage of modular programming is that we can perform modular debugging. We make a list of modules that might be causing the bug. We can then create new test routines to stabilize these modules and debug them one at a time. Unfortunately, sometimes all the modules seem to work, but the combination of modules does not. In this case we study the interfaces between the modules, looking for intended and unintended (e.g., unfriendly code) interactions.

4.5.2. Single Stepping

Many debuggers allow you to set the program counter to a specific address then execute one instruction at a time. The debugger provides three stepping commands StepStepOver and StepOut commands. Step is the usual execute one assembly instruction. However, when debugging C we can also execute one line of C. StepOver will execute one assembly instruction, unless that instruction is a subroutine call, in which case the debugger will execute the entire subroutine and stop at the instruction following the subroutine call. StepOut assumes the execution has already entered a subroutine, and will finish execution of the subroutine and stop at the instruction following the subroutine call.

4.5.3. Breakpoints

A breakpoint is a mechanism to tag places in our software, which when executed will cause the software to stop. Normally, you can break on any line of your program.

One of the problems with breakpoints is that sometimes we have to observe many breakpoints before the error occurs. One way to deal with this problem is the conditional breakpoint. To illustrate the implementation of conditional breakpoints, add a global variable called Count and initialize it to 32 in the initialization ritual. Add the following conditional breakpoint to the appropriate location in your software. Using the debugger, we set a regular breakpoint at bkpt. We run the system again (you can change the 32 to match the situation that causes the error.)

           PUSH {R1, R2}   ; save R1 and R2

           LDR  R2, =Count ; R2 = Count

           LDR  R1, [R2]   ; R1 = Count

           SUBS R1, R1, #1 ; Count = Count – 1

           STR  R1, [R2]   ; store to Count

           BNE  DEBUG_skip ; if Count != 0, skip

DEBUG_bkpt NOP             ; put breakpoint here

DEBUG_skip POP {R1, R2}    ; restore R1 and R2

 

if(--Count==0)

   bkpt    

 

Notice that the breakpoint occurs only on the 32nd time the break is encountered. Any appropriate condition can be substituted. Most modern debuggers allow you to set breakpoints that will trigger on a count. However, this method allows flexibility of letting you choose the exact conditions that cause the break.

4.5.4. Instrumentation: Print Statements

The use of print statements is a popular and effective means for functional debugging. One difficulty with print statements in embedded systems is that a standard “printer” may not be available. Another problem with printing is that most embedded systems involve time-dependent interactions with its external environment. The print statement itself may be so slow, that the debugging process itself causes the system to fail. In this regard, the print statement is intrusive. Therefore, throughout this book we will utilize debugging methods that do not rely on the availability of a standard output device.

 

4.5.5. Instrumentation: Dump into Array without Filtering

There are three limitations of using print statements to debug. First, many embedded systems do not have a standard output device onto which we could stream debugging information. A second difficulty with print statements is that they can significantly slow down the execution speed in real-time systems. The bandwidth of the print functions often cannot keep pace with the real-time execution. For example, our system may wish to call a function 1000 times a second (or every 1 ms). If we add print statements to it that require more than 1 ms to perform, the presence of the print statements will cause the system to crash. In this situation, the print statements would be considered extremely intrusive. Another problem with print statements occurs when the system is using the same output hardware for its normal operation, as is required to perform the print function. For example, your watch may have an LCD, but that display is used to implement the watch functionality. If we output debugging information to the LCD, the debugger output and normal system output are intertwined.

To solve these limitations, we can add a debugging instrument that dumps strategic information into an array at run time. We can then observe the contents of the array at a later time. One of the advantages of dumping is that the JTAG debugger allows you to visualize memory even when the program is running. So this technique will be quite useful in systems with a JTAG debugger. Assume happy and sad are strategic 8-bit variables. The first step when instrumenting a dump is to define a buffer in RAM to save the debugging measurements.

SIZE     EQU 20

HappyBuf SPACE SIZE

SadBuf   SPACE SIZE

Cnt      SPACE 4

#define SIZE 20

unsigned char HappyBuf[SIZE];

unsigned char SadBuf[SIZE];

unsigned long Cnt;

 

The Cnt will be used to index into the buffers. Cnt must be initialized to zero, before the debugging begins. The debugging instrument, shown in Program 4.3, dumps the strategic variables into the buffers. When writing debugging instruments it is good style to preserve all registers.

Save PUSH {R0-R3,LR}

     LDR  R0,=Cnt  ;R0 = &Cnt

     LDR  R1,[R0]  ;R1 = Cnt

     CMP  R1,#SIZE

     BHS  done     ;full?

     LDR  R3,=Happy

     LDRB R3,[R3]  ;R3 is happy

     LDR  R2,=HappyBuf

     STRB R3,[R2,R1] ;save happy

     LDR  R3,=Sad

     LDRB R3,[R3]  ;R3 is sad

     LDR  R2,=SadBuf

     STRB R3,[R2,R1] ;save sad

     ADD  R1,#1

     STR  R1,[R0] ;save Cnt 

done POP  {R0-R3,PC}

void Save(void){

  if(Cnt < SIZE){

    HappyBuf[Cnt] = happy;

    SadBuf[Cnt] = sad;

    Cnt++;

  }

}

Program 4.3. Instrumentation dump.

Next, you add BL Save statements at strategic places within the system. You can either use the debugger to display the results, or add software that prints the results after the program has run and stopped.

4.5.6. Instrumentation: Dump into Array with Filtering.

One problem with dumps is that they can generate a tremendous amount of information. If you suspect a certain situation is causing the error, you can add a filter to the instrument. A filter is a software/hardware condition that must be true in order to place data into the array. In this situation, if we suspect the error occurs when another variable gets large, we could add a filter that saves in the array only when the variable is above a certain value. In the example shown in Program 4.4, the instrument dumps only when sad is greater than 100.

Save PUSH {R0-R3,LR}

     LDR  R3,=Sad

     LDRB R3,[R3]  ;R3 is sad

     CMP  R3,#100

     BLS  done     ;assuming unsigned

     LDR  R0,=Cnt  ;R0 = &Cnt

     LDR  R1,[R0]  ;R1 = Cnt

     CMP  R1,#SIZE

     BHS  done     ;full?

     LDR  R2,=SadBuf

     STRB R3,[R2,R1] ;save sad

     LDR  R3,=Happy

     LDRB R3,[R3]  ;R3 is happy

     LDR  R2,=HappyBuf

     STRB R3,[R2,R1] ;save happy

     ADD  R1,#1

     STR  R1,[R0] ;save Cnt 

done POP  {R0-R3,PC}    

void Save(void){

  if(sad > 100){

    if(Cnt < SIZE*2){

      HappyBuf[Cnt] = happy;

      SadBuf[Cnt] = sad;

      Cnt++;

    }

  }

}

Program 4.4. Instrumentation dump with filter.

4.5.7. Prove that our system is functioning as intended.

Video 4.4. Making the case for Functional Debugging

When embedded systems are deployed in safety critical situations we need to document how the system was tested, and provide proof it is functioning as intended. We will illustrate the process with the example from module 2. In Lab02 the goal was to create a system that toggled the light at 5 Hz. This means the red LED should be on for 0.1 sec and off for 0.1 sec. If we look at the LED with our eyes it looks like it is running correctly. If we look at the signal on the oscilloscope or logic analyzer, again it looks correct. But can we prove it? In this first debugging process we will dump the value of PF1 in one array and the time difference in a second array, see Program 4.5. When we run this program, it reveals all 49 measurements where the average time difference is 1,599,996 bus cycles, which is 0.09999975 seconds, which is close to the desired time of 0.1 second.

Video 4.5. Functional Debugging

// first data point is wrong, the other 49 will be correct

uint32_t Time[50];

uint32_t Data[50];

int main(void){  uint32_t i,last,now;

                  // initialize PF0 and PF4 and make them inputs

  PortF_Init();   // make PF3-1 out (PF3-1 built-in LEDs)

  SysTick_Init(); // initialize SysTick, runs at 16 MHz

  i = 0;          // array index

  last = NVIC_ST_CURRENT_R;

  while(1){

    Led = GPIO_PORTF_DATA_R;   // read previous

    Led = Led^0x02;            // toggle red LED

    GPIO_PORTF_DATA_R = Led;   // output

    if(i<50){

      now = NVIC_ST_CURRENT_R;

      Time[i] = (last-now)&0x00FFFFFF;  // 24-bit time difference

      Data[i] = GPIO_PORTF_DATA_R&0x02; // record PF1

      last = now;

      i++;

    }

    Delay();

  }

}

Program 4.5. Instrumentation to record the first 49 time differences (debugging shown in bold).

However compelling this data is, it doesn’t prove it is always 0.1 sec. Let’s further specify the desire is to create a system that is accurate to ±0.01%. This means any time difference 1,600,000±160 cycles is acceptable. In Program 4.6 we will count the number of times the time difference is unacceptable. If we run this program for a month we can observe its behavior over 25 million times. Furthermore, we can leave this debugging code into the deployed system, and verify the system is running as expected for the entire life of the system.

// first data point is wrong, the others will be correct

int32_t Errors;

#define CORRECT 1600000

#define TOLERANCE 160

int main(void){  uint32_t last,now,diff;

                  // initialize PF0 and PF4 and make them inputs

  PortF_Init();   // make PF3-1 out (PF3-1 built-in LEDs)

  SysTick_Init(); // initialize SysTick, runs at 16 MHz

  Errors = -1;    // no errors (ignore first measurement)

  last = NVIC_ST_CURRENT_R;

  while(1){

    Led = GPIO_PORTF_DATA_R;   // read previous

    Led = Led^0x02;            // toggle red LED

    GPIO_PORTF_DATA_R = Led;   // output

    now = NVIC_ST_CURRENT_R;

    diff = (last-now)&0x00FFFFFF;  // 24-bit time difference

    if((diff<(CORRECT-TOLERANCE))||(diff>(CORRECT+TOLERANCE)){

      Error++;

    }

    last = now;

    Delay();

  }

}

Program 4.6. Instrumentation to count the number of mistakes (debugging shown in bold).

One interesting feature we could add to this system is to implement the Error count in ROM. The flash ROM allows the running program to change its value. The disadvantage of storing data in ROM is it takes over 1ms to cause the change. In situations where we have infrequent but important data, this 1ms overhead is not significant. The advantage of storing infrequent but important debugging information in ROM is that this data is available even if power is removed and restored. For example, if the embedded system were to be involved in a loss of life accident, the data stored in ROM could be recovered to determine if any run-time errors might have contributed to the accident. Conversely, this data stored in ROM could verify that no errors in the operation of the embedded system had occurred prior to the accident. If you wish to write to flash ROM, look at example projects called “flash” on http://users.ece.utexas.edu/~valvano/arm/.

 

Reprinted with approval from Introduction to Embedded Systems, 2022, ISBN: 978-1537105727

Creative Commons License
Embedded Systems - Shape the World by Jonathan Valvano and Ramesh Yerraballi is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Based on a work at http://users.ece.utexas.edu/~valvano/arm/outline1.htm.