Byron is an independent firmware developer specializing in microprocessor and DSP design and development for data acquisition, control, and Internet appliances. He can be reached at [email protected]
Product development cycles are market driven, and market demands often require vendors to compress development schedules. One approach to this is to simultaneously develop similar products, yet with varying levels of product complexity. However, scheduling pressures coupled with increased product complexity can be a recipe for disaster, resulting in slipped schedules and missed opportunities. Consequently, vendors are always on the alert for silver bullets, yet as developers, we know that they don't exist. That said, it is still in our best interest to seek better ways of compressing development cycles, and one way to do this is to port existing products to new hardware platforms, adding new features along the way. This is the approach we used to demonstrate a proof-in-concept when porting a legacy security application to a new hardware platform.
Our firm was hired to make enhancements to the client's existing 6502-based product, and we quickly realized that this platform was running out of steam. Specifically, the proposed features would significantly impact performance. Consequently, we proposed three options for fixing this problem:
- Completely rewriting the application on the current hardware.
- Rewriting the application on a new, higher performance hardware.
- Migrating portable portions of the application to the new hardware.
After considering the options, we decided to port to new hardware.
Like most firmware projects, this project consisted of software and hardware, specifically the Quadros System's RTXC Real-Time Operating System (http://www.quadros.com/) and test code that we ported to NorthPole Engineering's NPE-167 Single Board Computer (http://www.npe-inc.com/), which is based on the Infineon C167 processor.
The C167 supports a 16-channel, 10-bit A/D converter, two 16-channel capture and compare units, four-channel PWM, two general-purpose timer units, asynchronous/ synchronous serial channel (USART), high-speed synchronous serial channel, watchdog timer, bootstrap loader, 111 I/O lines, 2K internal RAM, 16 priority-level interrupt system, an eight-channel Peripheral Event Controller (PEC), and a Controller Area Network (CAN) 2.0B specification. In its basic configuration, the NPE-167 (see Figure 1) contains both 512K of Flash and SRAM. Memory is expandable to 17 MB of combined Flash and SRAM. It also supports a real-time clock, RS-232 serial port, and modified Serial Peripheral Interface (SPI) bus used to control four I/O cards.
To bring up a real-time operating system (RTOS) on a new board, most vendors provide Board Support Packages (BSPs)software and documentation that provide guidance on getting the RTOS to execute on new hardware. RTXC's BSP for the C167 comes with the source code for the RTOS kernel, a system-generation utility (RTXCgen) for configuring the number of tasks, timers, and resources used for the specific application. It also includes documentation, some source-code examples, and makefiles for building the kernel and a sample application.
The Real-Time eXecutive kernel (RTXC) supports three kinds of priority-based task scheduling: preemptive (the default), round-robin, and time-slice.
RTXC is robust, supports hard deadlines, changeable task priorities, time and resource management, and intertask communication. It also has a small RAM/ROM code footprint, standard API interface, and is implemented in many processors.
RTXC is divided into nine basic components: tasks, mailboxes, messages, queues, semaphores, resources, memory partitions, timers, and Interrupt Service Routines (ISRs). These components are further subdivided into three groups that are used for intertask communication, synchronization, and resource management. Moreover, component functionality is accessed via the standard API interface.
Porting Activities Overview
Porting an RTOS to a new board requires four activities:
- Determining the system's architecture.
- Figuring out what files to change based on the architecture.
- Making changes to the files; this includes writing the code.
- Creating test code and exercising the board to ensure that the RTOS is working properly.
The first activity is design related, while the others are implementation related. Moreover, the last three activities require an understanding of the new hardwareknowing the specifics of what needs to happen to make the RTOS interact with the board.
The purpose of determining the system architecture requirements is to identify the hardware and software components that need modifying to get the RTOS up and running on the NPE-167 board.
For most porting projects, hardware components include I/O, memory, timers, and other unique peripherals. For this project, these components are no different. We had the I/O ports that control the LEDs, CAN bus, serial communication, memory selection, and card-slot selection. Memory had both Flash and SRAM. Memory is selected through the I/O component using the SPI bus. Therefore, I/O and memory selection are interrelated. For this project, we also had to identify the timer to run RTXC's real-time clock. The real-time clock is the master timer used for all RTOS-based time-keeping functions. Additionally, for this project, we were not going to use any other on-chip peripherals.
The best way to identify hardware components is to study the board's schematics. Examining the NPE-167 board revealed that the I/O ports would be key for this project. Why? Because this board used the processor's general-purpose ports to handle switches to control CAN bus operation, the board's operating mode, control LED outputs, and memory selection. I/O cards were controlled via the SPI bus, rather than I/O ports.
Ports can be configured as either inputs or outputs. Examination of the NPE-167 board showed that 17 ports are used. Eleven ports are used as switch inputs. From the schematic we saw that switches 1-7 were used to set the MAC address for the CAN device. CAN bus speed is controlled by switches 8-9, while the board operating mode is controlled by switches 11-12. Switch 10 is not used. Four ports control the LEDs. There are three in total. One LED is green, one red, and the third bicolor. Thus, four outputs are required to control the three LEDs. Finally, two output ports are used as page selection for extended memory.
Referring to the schematic, we saw that the NPE board addresses up to 512K of memory before having to make use of the page-selection ports. Although we would configure the page-selection ports for the porting process, we didn't have to use them because the total code footprint of the kernel, plus test code, is 107K. RTXC's kernel is about 76K, and the porting test code fits within another 31K. In short, we would only use about 1/5 of the default memory to validate the porting process.
The last necessary component for the port was to determine which timer to use as the master time base. Timers are internal on the C167 processor, so they don't show up on the schematic. So we had two optionschoose a timer and write the code for that timer, or use the BSP default timer. RTXC's C167 BSP uses a timer in its configuration. A trick to simplify the initial porting process is to use the default timer that the BSP uses. Reviewing the BSP documentation, we discovered that it uses timer 6 for the master timer. Once we determined the components associated with the porting process, we could turn our attention to figuring out which files needed to be changed.
We knew from the previous step that 11 ports were used for input and six ports for output. Because these were general-purpose I/O ports, they needed to be initialized to work as either inputs or outputs. This gave us an idea of where NPE-specific initialization code needed to gospecifically, initialization code to set up these ports goes in the startup code. For this project, initialization code was located in the cstart.a66 file that is located in the Porting directory. Listing One is the code that configures the NPE-167 board I/O. Once configured, I/O can be used by higher level RTOS and API functions. Once we figured out where the I/O changes go, we needed to turn our attention to discovering and setting up the master timer.
BSP set up the master timer for us because we were using default timer 6. Setup code for this timer is located in cstart.a66 and rtxcmain.c. Listing Two is a snippet of the RTXC-specific code. After analyzing the architecture requirements, we discovered that the only file to change for porting the NPE-167 board was cstart.a66. Granted, we knew we would have to change other files as well, but those files are application specific.
Changing Files and Writing Code
This brought us to the third step, which was straightforward because we knew what needed to be changed and where. Recall that all changes for basic porting functionality occurred in cstart.a66. We also needed to write the code for initialization. We wrote code to initialize the switches to handle CANbut no other code to deal with it because it is not used in the basic port. For specifics, look at cstart.a66 and search for npe and rtxc labels to find code changes specific to this port. Keep in mind, when porting to new hardware you may want to adopt a similar strategy for partitioning the code for hardware- and RTOS-specific changes. That is because partitioning code through the use of labels helps with code maintainability.
Finally, we needed to create some test code to test our port. Building the test code application was a two-step process:
- We compiled the RTXC kernel into a library object (rtxc.lib).
- We compiled the test code and link in rtxc.lib to create the executable.
There are two directories for generating the test code, and they are stored at the same level in the hierarchy. Moreover, all files for creating rtxc.lib are located in the kernel directory. Alternatively, test code-specific files are located in the Porting directory.
The RTXCgen utility creates a set of files corresponding to each RTOS component. For instance, application queues are defined in three files: cqueue.c, cqueue.h, and cqueue.def. The same holds true for tasks, timers, semaphores, mailboxes, and the rest. Changes to the number of RTOS components are handled by this utility. For example, if we wanted to change the number of tasks used by the test code, we use RTXCgen to do it. Figure 2 shows the contents of the task definition file for the test code application. Test code files created by RTXCgen are placed in the Porting directory. Once RTXCgen has defined the system resources, we are ready to build the project.
Creating the executable test code requires the build of two subprojectsthe kernel and test code. We performed builds using the Keil Microvision IDE (http://www.keil.com/). Keil uses project files (*.prj files) to store its build information. RTXC kernel creation consists of building the code using the librtxc.prj file located in the kernel directory. Evoking the librtxc project compiles, links, and creates a librtxc object in the kernel directory. Building the test code is accomplished using the NpeEg.prj file stored in the Porting directory. Invoking the NpeEg project compiles and links files in the Porting directory, and links the librtxc object in the kernel directory. The resulting executable is then placed in the Porting directory as well. Once the test code was fully built, we were ready to test the board port.
The test code is a simple application used to validate the porting process. Most of the test code is located in main.c located in the Porting directory. The application works by starting five taskstwo user and three system. User tasks execute alternatively, while system tasks execute in the background. One user task begins running. It then outputs data via one of the system tasks to the console. Next, it signals the other to wake up, and it puts itself to sleep, thus waiting for the other task to signal it to wake up again. Figure 3 shows the executing test code.
That's pretty much it. Porting software to a new hardware board doesn't need to be hard. With a firm plan (and following this simple process), porting just got a lot easier.
Thanks to Joe Tretter and Rick Gibbs at Northpole Engineering for their assistance.
; NPE specific code in cstart.a66 ; ; NPE-167 Special Function Register Addresses P2 DEFR 0FFC0H DP2 DEFR 0FFC2H ODP2 DEFR 0F1C2H P7 DEFR 0FFD0H DP7 DEFR 0FFD2H ODP7 DEFR 0F1D2H PICON DEFR 0F1C4H CC8IC DEFR 0FF88H EXICON DEFR 0F1C0H T6CON DEFR 0FF48H ;---------------------------------------------------------------- ; HARDWARE INITIALIZATION FOR NPE-167 A ; This code initializes the processor to work with the peripherials ; on the NPE-167 A. $IF (WATCHDOG = 1) SRVWDT ; SERVICE WATCHDOG $ENDIF BCLR T6CON.6 ; Shut off timer. ; Initialize Ports 2,3,7 and 8 as standard TTL levels. MOV R5,0 MOV PICON,R5 ; ; Initialize Port 2. ; Set Output = P2.0: IO Bus Reset ; = P2.1, P2.2: System LED ; = P2.3, P2.4: CAN LED ; = P2.6, p2.7: Memory Page Select ; ; Set Input = P2.10, P2.11: CAN Speed Select (SW8, SW9) ; MOV R5,#001Fh ; Set outputs to off. MOV P2,R5 MOV R5,#0001h ; IO is open drain. MOV ODP2,R5 MOV R5,#00DFh ; Set output direction. MOV DP2,R5 ; ; Initialize Port 7. ; Set Output = P7.0 - P7.3: IO Bus Slot Select. ; MOV R5,#000Fh ; Set outputs to off. MOV P7,R5 MOV R5,#000Fh ; IO is open drain. MOV ODP7,R5 MOV R5,#000Fh ; Set output direction. MOV DP7,R5 ; ; Setup IO Interrupt (EX0IN). ; Disable external interrupts and set Interrupt level to 7, ; group level to 3, negative edge. ; MOV R5,#001Fh MOV CC8IC,R5 MOV R5,#0001h OR EXICON,R5Back to article
;======================================================================== ; ** Beginning of RTXC specific code ** $IF MICROVISION ;======================================================================== ; NULL STACK SIZE DEFINITION ; ; define the size of the stack for the null task. ; NOTE: Ensure you modify the 'C' level constant of the same name in ; the RTXCOPTS.H file. ;---------------------------------------------------------------- NULLSTKSZ EQU 80H $ELSE $INCLUDE(rtxcopts.inc) ; include kernel assembly definitions $ENDIF ; ** END RTXC specific code ** ;========================================================================= ;========================================================================= ; ** Beginning of RTXC specific code ** ; This user stack is used only for startup and entry into main() USTSZ EQU 40H ; set User Stack Size to 64 Bytes. ; ** END RTXC of RTXC specific code ** ;========================================================================= ;========================================================================= ; ** Beginning of RTXC specific code ** EXTRN nullstack:WORD EXTRN DPP3:DPP1_INITVALUE:WORD EXTRN DPP3:DPP2_INITVALUE:WORD ; Initialize the 'C' variables for task frame initialization MOV DPP1_INITVALUE, DPP1 MOV DPP2_INITVALUE, DPP2 $IF NOT TINY MOV R0, #POF nullstack ; restore user stack pointer MOV R5, #PAG nullstack MOV DPP2,R5 NOP BSET R0.0FH ; User stack uses DPP1 $ELSE MOV R0, #nullstack ; restore user stack pointer $ENDIF MOV R10, #NULLSTKSZ ADD R0, R10 ; get to top of stack ; ** END RTXC of RTXC specific code ** ;===============================================================Back to article