Designing An Arm-Based Multithreaded Video/Audio/ Motion Recording System - Part 2 - Embedded.com

Designing An Arm-Based Multithreaded Video/Audio/ Motion Recording System – Part 2

[Editor's Note: InPart 1, the author described the basic physical parameters of avideo/audio/motion recording system (VAM) and the basic hardware andsoftware building blocks that will be needed before actualimplementation and programming of the application.]

Our implementation will be simplified because we are primarilyinterested in developing a control structure for this system. Thus, wewill omit all file handling details, represent files as arrays, andsimulate capture of data once per second. (An actual implemented systemwould capture data about 20 to 40 times per second.) For convenience,we will represent each clock timer-tick as one second.

For this system, we will display information on the screen to showwhen events are generated and how they are processed. We will alsodisplay summary information on a periodic basis. Figure 15 below contains samplediagnostic output for our system that we could use during development.

Figure15. Sample output produced by VAM system.

We will arbitrarily schedule a summary of system statistics every1,000 timer-ticks. Note that in Figure15 , warning events occur at times 410 and 820. Recall that awarning event has priority 3. Also, an unsafe event occurs at time 760and a manually triggered event occurs at time 888. When the systemsummary is displayed at time 1,000, we note that the four detectedevents have been stored in protected memory. The repeated data valuesof 4660 (or 0x1234) are used only to suggest the capture of data fromthe VAM unit. Figure 16 below contains an overview of this structure.

Figure16. Basic structure for the VAM system.

We will begin by creating the declarations, definitions, andprototypes needed for our system. Figure17, below contains the first part of this section. The values wehave chosen for the stack size, memory byte pool size, the size of theprotected memory, and the size of the temporary memory can be modifiedif desired.

Figure17. Declarations and definitions—Part 1.

Many of the entries in Figure 17 aredefinitions of public resources, such as threads, timers, the messagequeue, the mutex, and the memory byte pool. (This is a good use of amemory byte pool because space is allocated from the pool only once,thus eliminating fragmentation problems. Furthermore, the memoryallocations are of different sizes.

Figure 18 below contains thesecond part of the program section, devoted to declarations,definitions, and prototypes. We declare the counters, variables, andarrays for our system, as well as the prototypes for our thread entryfunctions, the timer expiration functions, and the function to displayperiodic system statistics.

Figure18 Declarations, definitions, and prototypes—Part 2.

The two arrays declared in Figure18 represent file systems that would be used in an actualimplementation. The array named temp_array represents the temporarymemory used by the system. As noted earlier, the primary purpose oftemporary memory is to provide an ongoing repository of video, audio,and motion data.

When an event is detected or manually triggered, the 12 seconds ofdata before and after that event are copied to protected memory.Temporary memory is overwritten repeatedly because it is limited insize. Figure 19 below illustrates the organization of temporary memory.

Figure19. Organization of temporary memory.

The array named protected_memory represents the protected memory inour system. Each entry in this array represents an event that hasoccurred. Figure 20 below illustrates the organizationof protected memory. Each stored event contains the time the eventoccurred, the event priority, and the captured data for the event.

Figure20 Organization of protected memory.

The size of the protected memory is relatively small and isspecified as MAX_EVENT in Figure 17 .Having a small size is reasonable because the number of vehicle eventsshould also be relatively small.

This is the entry into the ThreadX kernel. Note that the call totx_kernel_enter does notreturn, so do not place any processing afterit. Figure 21  below contains the main entry point for the VAM system.

Figure21. The main entry point.

The next portion of our system is the application definitionssection, which we will divide into two parts. In the first part, wewill define the public resources needed for our system. This includesthe memory byte pool, the three threads, the nine timers, and themessage queue. Figure 22, below  contains the first part of the applications definitions section.

Figure22. Application definitions—Part 1.

This part of our system consists of the application definitionfunction called tx_application_define .This function defines all theapplication resources in the system. This function has a single inputparameter, which is the first available RAM address. This is typicallyused as a starting point for run-time memory allocations of threadstacks, queues, and memory pools. The first declaration in Figure 22 is the following:

CHAR   *byte_pointer;

This pointer is used when allocating memory from the byte pool forthe threads and for the message queue. We then create the memory bytepool, as follows:

tx_byte_pool_create(&my_byte_pool,”my_byte_pool”,
    first_unused_memory, BYTE_POOL_SIZE);

We need to allocate stack space from the byte pool and create theinitializer thread, as follows:

tx_byte_allocate(&my_byte_pool, (VOID**) &byte_pointer,
    STACK_SIZE, TX_NO_WAIT);

tx_thread_create(&initializer,”initializer”,
                           initializer_process, 0,
                           byte_pointer, STACK_SIZE, 11, 11,
                           TX_NO_TIME_SLICE, TX_AUTO_START);

We assign the initializer thread the highest priority of the threethreads in our system. This thread needs to perform its operationsimmediately, and then it terminates. The other two threads continueoperating for the life of the system. We create the data_capture thread and the event_recorder thread in a similarfashion.

However, we assign the event_recorder thread a higher priority than the data_capture thread because it isessential that the event recording operation be performed in a timelymanner. (The actual values of the priorities are not important,provided that the initializer thread has the highest priority, and the event_recorder thread has a higherpriority than the data_capture thread.)

Note that we use neither the time-slicing option nor thepreemption-threshold option for any of these threads. However, theevent_recorder thread is giventhe TX_DON'T_START option, but the other two threads are given the TX_AUTO_START option.

The second part of the applications definitions section appears in Figure 23, below ; this part containsdefinitions of our timers and the message queue.

Figure23. Application definitions—Part 2.

We create nine timers and one message queue in Figure 23 . There are four timersdedicated to simulating interrupts, four timers to schedule eventrecording, and one timer to display system statistics. We first createthe crash_interrupt timer, which simulates crash events. We arbitrarilysimulate crash events every 1,444 timer-ticks—recall that we simulateone second with one timer-tick. Following is the definition of the crash_interrupt timer.

tx_timer_create(&crash_interrupt, “crash_interrupt”, crash_ISR,
                        0x1234, 1444, 1444, TX_AUTO_ACTIVATE);

The expiration function associated with the crash_interrupt timer is crash_ISR , which we will define inthe next section of our system. This function is activated every 1,444timer-ticks. We do not use a parameter in the expiration function, sowe will specify an arbitrary argument value when we create the timer—inthis case, the value 0x1234.

The unsafe_interrupt timer,the warning_interrupt timer,and the manual_interrupt timer are createdin a similar manner. However, we specify different expiration valuesfor each of these timers. Note that we give each of these timers the TX_AUTO_ACTIVATE option. We createfour timers to schedule the copying of an event from temporary memoryto protected memory. We first create the crash_copy_scheduler timer asfollows:

tx_timer_create(&crash_copy_scheduler, “crash_copy_scheduler”,
                        crash_copy_activate, 0x1234, 12, 12, TX_NO_ACTIVATE);

When an event is detected (or generated, in our system), theassociated ISR activates the corresponding scheduling timer—note thatthe TX_NO_ACTIVATE option isspecified for each of the scheduling timers. Each of the schedulingtimers has an expiration value of 12 timer-ticks, so it will expireexactly 12 timer-ticks after an event has been detected.

As before, we pass a dummy argument to the expiration function. Theunsafe_copy_scheduler timer,the warning_copy_scheduler timer, and the manual_copy_scheduler timer are each created in a similar fashion.The timer to print system statistics on a periodic basis is created asfollows:

tx_timer_create (&stats_timer,”stats_timer”, print_stats,
                        0x1234, 1000, 1000, TX_AUTO_ACTIVATE);

We create this timer with the TX_AUTO_ACTIVATE option, so it expires every 1,000 timer-ticks. When this timer doesexpire, it invokes the expiration function print_stats, which displaysthe summary statistics.

The last definition in Figure 23 creates the message queue event_notice. We must first allocate spacefrom the memory byte pool for the message queue, and then create thequeue. Following is the creation of this queue:

tx_byte_allocate(&my_byte_pool,(VOID **) &byte_pointer,
     MAX_EVENTS*2*sizeof(ULONG), TX_NO_WAIT);
tx_queue_create (&event_notice,”event_notice”, TX_2_ULONG,
                          byte_pointer, MAX_EVENTS*2*sizeof(ULONG));

The expression

MAX_EVENTS*2*sizeof(ULONG)

computes the amount of space needed by the message queue, based onthe maximum number of events in protected memory, the size of eachmessage, and the size of the ULONG data type. We use this sameexpression to specify the amount of space for the queue when it iscreated. The tx_byte_allocate service creates a block of bytes specified by this expression; theblock pointer that receives the address of this block is pointed to by &byte_pointer .

We specify the TX_NO_WAIT option as the wait option. (The TX_NO_WAIT option is the only valid wait option when the tx_byte_allocate service is calledfrom initialization.)

The TX_2_ULONG optionspecifies the size of each message in the queue. The location of memoryspace for the queues is pointed to by byte_pointer. Figure 24 below illustrates the contents ofthe event_notice messagequeue.

Figure24. Message queue event_notice.

The frame index contains the location in temporary memory where theevent began. The event priority indicates the type of event thatoccurred.

The last portion of our system is the function definitions section,which we will divide into five parts. In the first part, we will definethe initializer entry function, which performs basic initializationoperations. The second part contains the definition of the data_capture_process entryfunction, which simulates data capture. The third part containsdefinitions of the crash_ISR expiration function and the crash_copy_activate expiration function.

The fourth part contains the definition of the event_recorder_process entryfunction, which copies an event from temporary memory to protectedmemory. The fifth part contains the print_stats expiration function, which displays system statistics at periodicintervals.

Figure 25  below contains the definition for the initializer entry function. This is thefirst function that is executed. This function merely initializes the frame_index and event_count global variables. Thesevariables point to the current position of temporary memory andprotected memory, respectively. If an actual file system were used, theinitializer function would have considerably more work to do.

Figure25. Function definitions part 1—initializer entry function.

Figure 26 below containsthe definition for the data_capture_process entry function. This function runs constantly and simulates the captureof data from the VAM unit every timer-tick. We will use the value0x1234 to suggest data that is being captured and we will get thecurrent value of the system clock.

We will then store those two values in the temp_memory array, whichrepresents the temporary memory file. The value of the frame_index isadvanced by one during each timer-tick, and it wraps around to thebeginning when it encounters the last position of the array.

Figure26. Function definitions part 2—data_capture_process entry function.

Figure 27 below containsdefinitions for the crash_ISR expiration function and the crash_copy_activate expirationfunction. These two functions are placed together because they areclosely related to each other. The crash_ISR function sends the frame_index value and the priority (i.e., 1 for a crash) to the event_notice message queue.

It then activates the crash_copy_scheduler timer, which expires in 12 seconds and then invokes the crash_copy_activate expirationfunction. The crash_copy_activate functionresumes the event_recorder thread and then deactivates the crash_copy_scheduler timer. We donot show the corresponding expiration functions for the unsafe,warning, and manual events because they are quite similar. Onedifference is that different priority values are associated with theseevents.

Figure27. Function definitions part 3: crash_ISR and crash_copy_schedulerexpiration functions.

Figure 28  below contains the definition for the event_recorder_process entry function,which is part of the event_recorder thread. When this thread is resumedby one of the scheduler timers, this function copies a crash event, anunsafe event, a warning event, or a manual event from temporary memoryto protected memory. Following are the operations performed by thisfunction:

1. Get the first message onthe queue.
2. Extract the frame index andpriority for the event from the message.
3 . Get the frame in temporarymemory (containing the time and captured data)pointed to by the frame index.
4. Compute the copy startingpoint, which is 12 seconds before the current frame.
5 . If protected memory is notfull, do the following:
    a. Get themutex.
    b. Storethe event time and priority in the next available position in protectedmemory.
    c. Store the24 seconds of frame data from temporary memory to protected memory.
    d. Incrementthe event counter.
    e. Releasethe mutex.

Figure28. Function definitions part 4: event_recorder entry function.

We will not implement the overwrite rules in the case when protectedmemory is full. These rules depend on the actual file system used, andare left as an exercise for the reader. When protected memory is full,we will display a message to that effect, but will not copy the event.

Figure 29  below containsthe definition for the print_stats expiration function, which is part of the timer called stats_timer.This timer expires every 1,000 timer-ticks and the function displays astatistical summary of the system.

Figure29 Function definitions part 5—print_stats expiration function.

Summary
We used only three threads for this case study. The initializer threadinitializes the system, the data_capture thread coordinates the captureof data from the VAM unit to temporary memory, and the event_recorder thread copies event data from temporary memory to protectedmemory.

We created the data_capture thread with the TX_AUTO_START option, so it wouldbecome ready immediately after initialization. We created the event_recorder thread with the TX_DON'T_START option, which meansit cannot start until it is resumed by an application timer. When the event_recorder thread completes itsdata copying operation, it suspends itself and remains suspended untilit isresumed by a timer.

We used one message queue to store information about events that hadoccurred. When the event_recorder thread is resumed, it takes the first message from the front of themessage queue. This message contains information about the event databeing copied from temporary memory to protected memory.

Although we used only one message queue in this case study, typicalapplications tend to use a number of message queues. We used one mutexto ensure that the event_recorder thread had exclusive access to protected memory before beginning thecopying operation.

We used one memory byte pool to provide space for the thread stacksand for the message queue. This application is an excellent use of amemory byte pool because we need memory space of different sizes forthe thread stacks and the message queue.

Furthermore, these entities remain in existence for the life of thesystem, so there is no possibility of the fragmentation problems thatcan occur when bytes are repeatedly allocated and released.

We also demonstrated several examples of communication betweenresources. For example, timers send data to the message queue, a threadreceives data from the message queue, a timer activates another timer,a timer resumes a thread, and a thread obtains ownership of a mutex.All these entities interact to form an efficient and effective system.

To read Part 1 inseries go to “Definingthe video, audio, control constraints.

Thisseries is reprinted in two parts from Chapter 14 of “Real-TimeEmbedded Multithreading, Using ThreadX and ARM” with permissionof Elsevier.

Dr. Edward L. Lamie isprofessoremeritus and former department chair of California State University'sComputer Science Department at the Stanislaus Campus. He directseducational services at Express Logic, where he is responsiblefor development and delivery of customer training.

Editor'snote:A complete listing of the system described here is included in Lamie'sbook as well as on the CD in the back of the book.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.