BBBCSIO

PRU Interrupt Host Example Code

 

About the PRU Interrupt To Host Mechanism

The Beaglebone Black PRU's can send interrupt style events to user mode host programs. This event manifests itself by the presence of 4 readable data bytes on one of the /dev/uio[0:7] files. If the event has not been sent then any code which attempts to read from that file will simply block (wait) until an interrupt is sent. The number of the UIO file (0 to 7) is representative of the number of the interrupt which has been sent. However, the interrupt number the PRU activates and the UIO file on which the event occurs is not strictly fixed. The UIO file on which any particular PRU event can appear can be set via configuration options in the PRU's Interrupt controller.

The BBBCSIO PRU_Driver class is designed to be able to transmit such PRU Interrupts to a C# program via a .NET event to which that program is a subscriber. In order to present a consistent interface, the PRU_Driver has a "Standard" mapping of PRU interrupts to UIO file output. This mapping is completely arbitrary (and, it must be said, probably unique to the BBBCSIO library) but has been structured to provide a certain logical operation.

The PRU_Driver class source code contains numerous lengthy comments regarding the "Standard" mapping, but the general idea is that if you write a 0 to bits [0-3] of Register 31 (R31) in the PRU you will get an event out on the file /dev/uio0 and this corresponds to the PRUEventEnum.PRU_EVENT_0. If you write a value of 1 to R31 bits [0-3] you get an event out on the file /dev/uio1 and this corresponds to the PRUEventEnum.PRU_EVENT_1. Thus, there is a simple mapping, a 6 in the PRU is PRU_EVENT_6 in the BBBCSIO PRU_Driver code - and so on.

Of course you still have to trap the PRU events in your C# program and this can be done synchronously (the program blocks until the event happens) and asynchronously (the program does other things and the interrupt appears as a C# event). The example code below shows you how to set both of these up.

NOTE: Since we are interacting with the PRU we also have to run code on the PRU and this code is necessarily specific to the example. The PRU PASM assembly language code for the example below can be found in the file BBBCSIOHelp_PRUInterruptHostExamplePASMCode.html

Warnings - READ THIS!!!

Always be aware that the armhf Linux running on the Beaglebone Black is not a real-time operating system. This means, for example, that although the PRU's are real time by the time the event gets down to the user space program for processing the process may have been pre-emptively swapped in and out many times by the kernel. The timing of the interrupt events you will receive in the C# code is absolutely not real time or deterministic.

An Example of the how a Host C# program can trap PRU Interrupt Events

The code below illustrates how to use the functions in the PRU_Driver class to launch a PRU program and interact with the events it sends. The comments in the example describe the operation of the code. Note that two of the events will trigger the PRUEvent_OnInterrupt event handler the example code for which is located at the bottom of this page.

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// A sample which illustrates the transmission of events from the PRU 
        /// to a user space C# program. 
        /// 
        /// In this example, the ASM binary to load is hard coded. It is designed
        /// to blink the User3 LED 10 times. Each time the LED goes on PRU_EVENT_1
        /// is generated, when the LED goes off PRU_EVENT_2 is generated. The
        /// termination of the PRU program is signaled by PRU_EVENT_0
        /// 
        /// The two LED ON/OFF events are handled asynchronously and the events
        /// will appear as calls to the PRUEvent_OnInterrupt. This code will
        /// NOT be running in the same thread as the code below.
        /// 
        /// Ultimately you will see the User3 LED blink and each time it turns on
        /// an 'X' will be written to the Console and a 'O' each time it turns off.
        /// The user space program will process all of these events while waiting
        /// for the main PRU termination signal.
        /// 
        /// </summary>
        /// <param name="pruID">The pruID</param>
        /// <param name="binaryToRun">The binary file to run</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void PRUInterruptHost(PRUEnum pruID)
        {
            // sanity checks
            if (pruID == PRUEnum.PRU_NONE)
            {
                throw new Exception("No such PRU: "+pruID.ToString());
            }
            string binaryToRun = "/home/debian/CSharpCode/PASMCode/PRUInterruptHost.bin";

            // build the driver
            PRUDriver pruDriver = new PRUDriver(pruID);

            // initialize the PRU interrupt subsystem. The two PRUs
            // share this so, if both PRUs are in use, we only call this
            // ONCE - before any and all other PRU Event configuration.
            pruDriver.InitInterruptsForAllPRUs();

            // setup an event to monitor the PRU termination
            pruDriver.EnablePRUHostEvent(PRUEventEnum.PRU_EVENT_0);
            // setup an event to monitor the LED being turned on
            pruDriver.EnablePRUHostEvent(PRUEventEnum.PRU_EVENT_1);
            // setup an event to monitor the LED being turned off
            pruDriver.EnablePRUHostEvent(PRUEventEnum.PRU_EVENT_2);

            // turn the LED ON event on. The PRUEvent_OnInterrupt function 
            // will receive the incoming event data and handle it
            pruDriver.PRUEventMonitorOn(PRUEventEnum.PRU_EVENT_1, new PRUInterruptEventHandlerUIO(PRUEvent_OnInterrupt));
        
            // turn the LED OFF event on. The same PRUEvent_OnInterrupt function 
            // will receive the incoming event data. We use the same one because the
            // PRUEvent_OnInterrupt function can figure out what event it is handling
            // from the information it receives when it is called
            pruDriver.PRUEventMonitorOn(PRUEventEnum.PRU_EVENT_2, new PRUInterruptEventHandlerUIO(PRUEvent_OnInterrupt));
      
            // NOTE: we do not monitor for the PRU TERMINATION event via
            // an event handler (although we could). We simply call 
            // WaitForEvent below to stall with a blocking read until
            // the PRU Sends us the signal.

            // run the binary
            pruDriver.ExecutePRUProgram(binaryToRun);

            // the PRU binary will send an event0 signal when it terminates.
            // While we are waiting for the termination we will be getting
            // event interrupts on our PRUEvent_OnInterrupt handler which
            // indicate actions the PRU is taking (the led is turned ON or OFF)
            pruDriver.WaitForEvent(PRUEventEnum.PRU_EVENT_0);

            // we still have to clear it 
            pruDriver.ClearEvent(PRUEventEnum.PRU_EVENT_0);
            Console.WriteLine(", PRU Program has terminated");

            // disable the event monitoring
            pruDriver.PRUEventMonitorOff(PRUEventEnum.PRU_EVENT_1);
            pruDriver.PRUEventMonitorOff(PRUEventEnum.PRU_EVENT_2);

            // close the driver, the code in the PRU remains running
            pruDriver.Dispose();
        }   
The PRUEventMonitorOn() statements in the above code subscribe to the PRU_Driver class OnInterrupt event and any events triggered by the PRU will appear in the named PRUEvent_OnInterrupt handler. Example code for the PRUEvent_OnInterrupt handler is given below. You can route more than one PRUInterruptHost to the same handler - however to do anything useful you will need to use the information passed in during the event to figure out which event was triggered.

Note that the code below executes in a different thread than the one in which the PRUInterruptHost function was created. You should not do any lengthy processing in the interrupt handler thread as no other interrupts can be processed until the current interrupt is cleared.

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// An PRUEvent interrupt event handler. This is where the data from 
        /// the PRU event arrives. Note the evPRUData object contains more
        /// detailed information than is the passed in arguments. This includes
        /// a reference to the PRUDriver object
        ///    
        /// NOTE:
        ///    Be aware that this handler method which accepts the data from 
        ///    an PRUDriver event is executing in the PRUDriver's event 
        ///    thread - not the main() thread. You have to be careful what you do 
        ///    in here to avoid the many issues associated with multi-threaded 
        ///    applications. 
        /// 
        /// NOTE: Be Quick. It is not advisable to take to long to process the 
        ///    incoming event data - you cannot receive another interrupt while 
        ///    processing the current one. The fact that a lost interrupt
        ///    happened can only be detected by noting gaps in the evCount
        /// 
        /// NOTE: You MUST clear the event in here or you will never get another
        /// 
        /// NOTE: this is NOT deterministic. You CANNOT trust the timing here
        ///       as this process may have been swapped in and out any number 
        ///       of times by the Kernel in an arbitrary manner before this
        ///       function is called.
        ///
        /// </summary>
        /// <param name="evPRUEvent">The PRU Event the event is configured on</param>
        /// <param name="evCount">The count of event</param>
        /// <param name="evDateTime">The date time of the event</param>
        /// <param name="evPRUData">The PRUEventHandler data structure</param>
        /// <history>
        ///    05 Jun 15  Cynic - Originally written
        /// </history>
        public void PRUEvent_OnInterrupt(PRUEventEnum evPRUEvent, uint evCount, DateTime evDateTime, PRUEventHandler evPRUData)
        {
            // here we process and take action on the incoming data
            // in this particular example we just write it out to the 
            // console
            if (evPRUData != null)
            {
                // this function could receive data from multiple
                // PRU interrupt events. To decide what action to take we could
                // look at the evPRUEvent or other information in the 
                // evPRUData object. 

                // just output something appropriate to the event we
                // are handling
                if (evPRUEvent == PRUEventEnum.PRU_EVENT_1)
                {
                    // the LED just got turned ON
                    Console.Write("X");
                }
                else if (evPRUEvent == PRUEventEnum.PRU_EVENT_2)
                {
                    // the LED just got turned OFF
                    Console.Write("O");
                }

                // we MUST call ClearEvent on the event
                if (evPRUData.PRUEventDriver != null)
                {
                    evPRUData.PRUEventDriver.ClearEvent(evPRUEvent);
                }
            }
        }
In the PRUEvent_OnInterrupt call, the PRUEventEnum and other useful data is passed in when the event is triggered.

The PRUInterruptHost would be launched via a line which looks like:

PRUInterruptHost(PRUEnum.PRU_0);