Generating C Code from a Simulink Model



Generating C Code from a Simulink Model

Generating C Code from a Simulink Model 1

Introduction 1

Setting up a Model for C Code Generation 3

Signal Properties 4

Configuration Parameters 9

TLC Selection 11

Hardware Implementation Pane 13

Generating Code 15

The Code Generation Process 18

File and Directory Naming Conventions 21

Integrating Generated C Code with Existing C Code 21

About the Shipping Hands-free Kit Code 23

Preparing the Legacy Code for Integration with Generated Code 24

Creating an MPLAB Project 26

Preventing User Source Code from Being Deleted 30

Changes to MAIN.C 32

Necessary Project Modifications 36

It Built! Are We Done? 42

Is There An Automated Way To Locate Source File Dependencies? 43

Everything About A Simulink Model You Never Wanted To Know 44

Code Customizations 46

Using Structures 47

Using #defines 51

Next Steps 53

Introduction

In this chapter we are going to start with the Simulink model we left off with from the previous chapter and generate C code for the Echo Canceller subsystem. We will take this code and integrate it into an existing Microchip project targeting a dsPIC30F6014A processor. The assumption here is that the Simulink model has performed reasonably well in terms of its Metrics during simulation. Now we want to see if that performance translates to the real DSP.

All of the files referred to in this workflow are accessed from an HTML example selector. You can open this example selector by running the included M-file run_exsel.m.

[pic]

This model we are going to be generating code for in this chapter is accessed via the “Macro” link from the example selector.

[pic]

This is our starting point model for C code generation. We need to first set it up for C code generation.

Setting up a Model for C Code Generation

There is always some set up work to be done before generating C code.

We will be generating code for one part of the overall Simulink model. Naturally the test bench is not in the picture when it comes to code generation. There are cases where you would include part of the test bench in the code generation step but those are not typical cases and as such are not covered in this document.

[pic]

This is the subsystem for which we are generating C code. Notice there are names below the signal lines a couple of antenna-looking symbols on the input and output ports.

Signal Properties

[pic]

Here is a larger view of the same subsystem. We covered how to set up each signal’s characteristics using the menu item Signal Properties in a previous chapter on Modeling an Echo Canceller.

The names rin_linear, sin_linear, and sout_linear will appear in the generated C code as arrays of 80 16 bit signed integers. How do we know that? Because we specified such parameters in our data dictionary, ec_fixed_setup.m. This was described in the previous chapter on test-benches.

|pic_frame_size = 80; |

|mpt_dimensions = [pic_frame_size 1]; |

|sin_linear = mpt.Signal; |

|sin_linear.SamplingMode ='Frame based'; |

|sin_linear.Dimensions = mpt_dimensions; |

|sin_linear.Description = 'Near end voice plus echo'; |

|sin_linear.DataType = 'fixdt(1,16,14)'; |

|sin_linear.RTWInfo.StorageClass = 'ImportedExtern'; |

This is a snippet from ec_fixed_setup.m. You can see that sin_linear is a fixed point object, signed, 16 bits wide, with 14 precision bits. It’s storage class is ‘ImportedExtern’. It’s frame-based with dimensions of 80 by 1. This would correspond to a declaration as follows in the generated C code, in ec_private.h.

extern int16_T sin_linear[80]; /* '/Sin' */

Why did we set up sin_linear to be an extern int instead of just int? The reason is that sin_linear was already declared in the existing or legacy C code in main.c so we just refer to the existing declaration in the generated code.

this is how sin_linear was originally declared in the legacy project

int sin_linear[80]; //

One declaration says int while the other uses int16_T. int16_T is typedef’d to int in rtwtypes.h so they are actually the same thing.

Optionally, you could have set the storage class for sin_linear in the Simulink model itself rather than using an m-file data dictionary approach.

[pic]

You can set the storage class for any Simulink signal from its Signal Properties dialog. The checkbox “Signal name must resolve to Simulink signal object” must be unchecked to set storage class in this dialog.

[pic]

In the MATLAB workspace browser you can find rin_linear along with all of the other variables used by our model. You must run the model before you see these variables since ec_fixed_setup.m is called from the Model Properties InitFcn callback. Double-click on rin_linear to see all of its properties.

[pic]

By double-clicking on rin_linear you see the same properties we set explicitly in our data dictionary M-file, ec_fixed_setup.m. You can over-ride the M-file here via this GUI. If you do however, remember the changes won’t back-propagate into ec_fixed_setup.m so consider them temporary changes.

|pic_frame_size = 80; |

|mpt_dimensions = [pic_frame_size 1]; |

|sin_linear = mpt.Signal; |

|sin_linear.SamplingMode ='Frame based'; |

|sin_linear.Dimensions = mpt_dimensions; |

|sin_linear.Description = 'Near end voice plus echo'; |

|sin_linear.DataType = 'fixdt(1,16,14)'; |

|sin_linear.RTWInfo.StorageClass = 'ImportedExtern'; |

It’s convenient to be able to control your data types programmatically in a text file that is external to your Simulink model. For instance, you could have 3 different M-files with 3 different sets of data types. Just run the M-file with the data types you want to test with and instantly your Simulink model is updated and ready to be tested. This is much quicker and reproducible than manually going into the separate dialogs and changing the data types.

Configuration Parameters

There is more to code generation setup than just describing the inputs and outputs. For that we go to the Simulation/Configuration Parameters.

[pic]To generate C code for an embedded target like a DSP chip you must use a fixed-step solver.

[pic]

At this point your optimization settings are not critical but these settings are a good place to start and they do impact the generated C code. Use the Help button at the bottom to get an in-depth description of what each selection does. Most of the defaults are the best choices for C code generation.

[pic]

This is the where you start setting up your C code generation options. But it doesn’t end here. A common misconception when generating C code is that only the settings under the Real-Time Workshop tab impact code generation. That is not true. The settings under Solver, Optimization, and Hardware Implementation impact the generated C code as well.

TLC Selection

Select ERT.TLC for the system target file if you purchased the Real-Time Workshop Embedded Coder. Otherwise you’re only choice will be GRT.TLC. You will have more flexibility in terms of the look and feel of the generated code with ERT.TLC.

[pic]Set up the main Real-Time Workshop pane as follows.

Target Selection

System Target file = ERT.TLC

Language = C

Documentation and traceability

Check Generate HTML Report

Check Launch report automatically

Check Code-to-block highlighting

Check Block-to-code highlighting (not available with GRT.TLC)

Makefile configuration

Build Process and Makefile configuration are not relevant to this example. We will be using the dsPIC compiler which is a separate step from the Simulink environment here. As a note, Microchip is currently working on an integrated link from MPLAB to Simulink to automate the build process from Simulink for dsPIC targets. This new link is not covered in this document. Please contact Microchip for complete details.

Custom storage class

Uncheck Ignore custom storage classes (default)

Check Generate code only.

We are only using Simulink here for the C code generation aspect of the work, not for compiling and linking. The Microchip MPLAB IDE will be used to initate compiling and linking.

[pic]

This is the Real-Time Workshop setup pane located under Simulation/Configuration Parameters. We checked both code-to-block highlighting and block-to-code highlighting. This is forwards and backwards code traceability. It allows you to match up a section in the code with a block in the model or a block in the model with a section in the code.

Hardware Implementation Pane

Because we are targeting a dsPIC DSP, we have to tell the code generation engine something about the data types supported by that processor. To do that go to the Hardware Implementation pane.

[pic]

In the Hardware Implementation pane we specify the size information for integers, bytes, shorts, characters, and the native word size. All of this information impacts how the C code gets generated. You will have to look up the data types for the processor you are using and tell Simulink here how big they are.

[pic]

The reference documentation on our dsPIC tells us sizes for each data type.

Generating Code

This is the moment we’ve been waiting for. Let’s generate C code for the echo canceller subsystem.

[pic]

Here is the subsystem we’ll generate C code for. Right click on this block and select Real-Time Workshop/Build Subsystem. Make sure MATLAB’s present working directory (pwd) contains your Simulink model. Otherwise you will get an error. It is an all too common error so be aware of it.

[pic]

This is the error you’ll see when attempting to generate code from a directory that does not contain the Simulink model file. It’s very easy to do this and generally will happen when you cd to the code generation directory, ec_ert_rtw, to inspect the results of the previous build. Just cd back to the Simulink’s model directory and code generation should proceed normally.

[pic]

Push Build when you see this dialog appear.

The Code Generation Process

In the MATLAB command window you will see the code generation process begin. Make sure the command window is visible before you press Build or else you won’t be to bring the command window to the foreground until the build process is complete. The Build process is a blocking process within the MATLAB and Simulink environment.

|### Starting Real-Time Workshop build procedure for model: ec |

|### Generating code into build directory: C:\work\experiments\dspic\echo_cancel\myec\ec_ert_rtw |

|### Invoking Target Language Compiler on ec.rtw |

| |

|Bunch of messages later…. |

| |

|### Writing header file ec_types.h |

|### Writing header file ec.h |

|. |

|### Writing source file ec.c |

|### Writing header file ec_private.h |

|### Writing source file ec_data.c |

|. |

|### Writing source file ert_main.c |

|### TLC code generation complete. |

|### Creating HTML report file ec_codegen_rpt.html |

|. |

| |

|### Processing Template Makefile: C:\R2007b\rtw\c\ert\ert_lcc.tmf |

|### Creating ec.mk from C:\R2007b\rtw\c\ert\ert_lcc.tmf |

|### Successful completion of Real-Time Workshop build procedure for model: ec |

The most important line is the last line, successful completion. You’re not done yet however. Now you have to marry the two worlds, the legacy C code with the C code generated from Simulink. That requires some work.

The code was generated in a sub-directory under your model’ directory in a directory named, ec_ert_rtw. The directory is created the first time you generate code for the ec subsystem. Note the first part of the directory name is the subsystem name, ec.

[pic]

Here are the files that were generated in the ec_ert_rtw subdirectory. We will be concentrating on the generated C and H files only. For a complete description of the other files generated, refer to the Real-Time Workshop documentation.

[pic]

Since we selected “Generate HTML Report” under Simulation/Configuration Parameters we also see a Real-Time Workshop Report. This is a hyper-linked document containing an HTML version of the code with links back to the Simulink model. Also included are other reports on the generated code. The generated HTML is located in a subdirectory under ec_ert_rtw called html.

File and Directory Naming Conventions

If the subsystem name you are generating C code for is the same as your present working directory, the name of the generated code and the generated subdirectory will have a “0” appended to the subsystem name. For example, the subsystem we are generating C code for is named “ec”. If our present working directory for this model is also named “ec”, then the name of the generated sub-directory under “ec” will be “ec0_ert_rtw” instead of “ec_ert_rtw” as you might want or expect. The generated C files in the ec0_ert_rtw directory will also have the “0” appended to the file names. If this is undesirable you will need to either rename your present working directory or rename the subsystem such that they don’t match.

Integrating Generated C Code with Existing C Code

After generating code from the ec subsystem, you have to integrate it with the legacy code base or project. In the case of this hands-free kit, there was an existing code base provided by Microchip. Part of the integration work took place already when we specified the storage class and data type of the input and output ports as 16 bit integers with an ImportedExtern storage class.

There is more than one way to go about integrating C code from a Simulink model with a legacy project. In this document we’ll just be illustrating one way relevant to this particular effort. It’s the most straight-forward method but not necessarily the best way for your particular project or company to go about doing so.

[pic]

Here is a top-level view at the existing project provided by Microchip for their hand-free kit development package. It included a number of C and H source files plus library files (.a files) and a linker command file (gld file).

[pic]

We are using Version 7.60 of the MPLAB IDE.

About the Shipping Hands-free Kit Code

The existing project had about 8 C source files, 2 header files, 2 library files, and one linker command file. The existing project was a complete project with its own implementation of an echo canceller. As it shipped however the dsPIC assembly-language based implementation of the echo canceller was determined to be numerically non-functional. Some very controlled experiments were performed to verify this. These tests were not dependent on amplifier gain, acoustics, speaker/microphone placement etc. The tests were purely digital in the software domain. These sames tests were also applied to the generated C code as we will see later.

The challenge was to integrate the generated C code into this existing project, preserving as much of the legacy code as possible while stripping away the parts we no longer needed.

We are keeping every C file in the legacy project. However most of the files were modified in at least some small way. We kept the peripheral control files necessary for communicating with the data communications interface (DCI). The interrupt service routines (ISRs) is preserved. The timer code is preserved. The trap code is preserved in case something goes wrong like a bad address being generated at run-time. We also preserved the linker command file, the .gld file for our particular dsPIC.

The dsPIC assembler based library files were removed, libaec.a and libns.a. These files were for echo cancellation and noise suppression respectively. It is in one or both of these files that were found to have problems. We also removed the serial peripheral interface code and any code making a reference to it, e.g. InitSPI1. We could do this since our echo canceller implementation is running in so-called Analog Mode on the evaluation board (see the dsPIC Hands-Free Kit documentation for a complete description of Analog Mode vs Digital Mode).

The existing project was very handy as it contains all the functioning boilerplate code to get started testing our own version of the echo canceller. Getting all the DSP’s registers initialized properly, writing ISRs, and architecting the code is a time-consuming process. Now we can just concentrate on our echo canceller design and not all of the other tedious embedded systems stuff. That is a relief.

Preparing the Legacy Code for Integration with Generated Code

In this section we are going to list the steps required to take the legacy code and massage it such that it can be used with the generated C code. The most important thing about these modifications is that they are only done once! This is critical. If the process you are following requires you to modify the either the legacy code or the generated code manually every time you generate code, then there is a problem with your process. There are two downsides to requiring a human be involved in the code generation process. One, it takes more time and two, it increases the risk for error. Make sure the code generation process is as automated as possible. With that point emphasized here are the steps.

Build code for the echo canceller subsystem as illustrated in the previous section. We call this a right-click build since you right-click on the subsystem of interest and select Real-Time Workshop/Build Subsystem. That creates the ec_ert_rtw subdirectory with the C and H file implementation of the echo canceller contained within.

[pic]

Press Build here.

Copy the legacy code into the Simulink build directory, namely, ec_ert_rtw. This is a controversial step and many folks will disagree with this. The philosophy taken here in this learning document was one of keeping it simple, not necessarily being the most elegant. By keeping all of our source files in one directory, it’s easy to share the project with anyone. It’s easy to know all of your project’s dependencies. Nothing is hidden. Just zip up the directory and send it. If you wish to keep the generated files in a separate directory from the legacy project, then that is a completely valid approach and probably the right thing to do on large projects. Whatever you do, do not refer to the include files in the Mathworks shipping directories in your C project. By placing a Mathworks directory on your MPLAB include path you loose knowledge of what header files are required to build the project and which aren’t. You also loose project portability, i.e. it’s going to be hard to move the project to another machine in the lab where Matlab is likely not installed. There are over 100 include files in many of the Matlab directories. Your project likely only requires a handful of them. Make sure you know which ones are required. We’ll cover the process of finding the required files in the following steps.

Creating an MPLAB Project

In MPLAB, create a new project using the Project Wizard or Project/New.

This sequence illustrates how to use the Project Wizard.

[pic]

Select the particular processor.

[pic]

Point to the compiler, assembler, and linker.

[pic]

Select a name and location for the project. We are placing it in the ec_ert_rtw directory along with the rest of our other files.

[pic]

Select the files to be included as part of the project. You don’t have to get every file right here. You can add and remove files later as well. Don’t worry if you don’t know every file at this time.

[pic]

Click Finish and your MPLAB project is created.

[pic]

After creating your new project, there will be two new files in the ec_ert_rtw directory. One is the project file, ec.mcp. The second is the workspace file, ec.mcw.

Add the special string “target specific file” to the first line of every legacy C and H file. Don’t add this to the generated C/H code. The presence of this string prevents the legacy C/H file from being deleted during the code generation process. This is explained in Chapter 14 of the Real-Time Workshop User’s Guide.

Preventing User Source Code from Being Deleted

Prior to Release 13 (Version 5.0), Real-Time Workshop did not delete any .c

or .h files that the user had placed in the build directory when rebuilding

targets. From Release 13 onward, all foreign source files are by default deleted

during builds, but can be preserved by following the guidelines given below.

If you put a .c/.cpp or .h source file in a build directory, and you want to

prevent Real-Time Workshop from deleting it during the TLC code generation

process, insert the string target specific file in the first line of the

.c/.cpp or .h file. For example,

/* COMPANY-NAME target specific file

*

* This file is created for use with the

* COMPANY-NAME target.

* It is used for ...

*/

...

[pic]

The MPLAB project should now look something like this. We’ve narrowed the window to focus on the included files. DCI.C, INT_PINS.C, LIN2ULAW.C, MAIN.C, TIMER.C, TRAPS.C and ULAW2LIN.C were legacy C files. The generated C files are EC.C and EC_DATA.C. The only legacy header file is PARAMS.H. The rest of the H files were generated. As we’ll see later more header files are required to build the output HEX file for flash.

Changes to MAIN.C

Some modifications to main.c in the legacy project were required to integrate the generated C code. The legacy real-time code was essentially structured as while forever loop that polled a flag to see if new data was available or not. If new data is ready, grab it and process it. If it’s not ready, just sit and wait for it to become ready. In other words, it’s a very simple single-tasking polling architecture.

|while(1) |

|{ |

|// Wait for all input data to be: |

|// (a) transmitted for previous frame |

|// (b) received for current frame |

|while ( (DCI_SPI_Done == 0) || (DCI_Done == 0) ); |

|DCI_SPI_Done = 0; |

|DCI_Done = 0; |

|// Suspend interrupts up to level 6 until data is copied |

|SRbits.IPL = 6; |

|// Copy input signal samples from buffers for processing |

|for(i = 0; i < FRAME; i++) |

|{ |

|sin_ulaw[i] = DCI_SPI_Rx[i]; |

|rin_ulaw[i] = DCI_Rx[i]; |

|DCI_SPI_Tx[i] = rin_ulaw[i]; |

|DCI_Tx[i] = sout_ulaw[i]; |

|} |

|// Allow interrupts after data copy is complete |

|SRbits.IPL = 0; |

|for(i = 0; i < FRAME; i++) |

|{ |

|sin_linear[i] = Ulaw2Lin((int)sin_ulaw[i]); |

|rin_linear[i] = Ulaw2Lin((int)rin_ulaw[i]); |

|} |

|if (nsflag == 1) |

|{ |

|NoiseSuppression(noise_supp_mem, sin_linear, y_scratch); |

|Nop(); |

|} |

|if (aecflag == 1) |

|{ |

|AcousticEchoCanceller(echo_mem, vad_memory, rin_linear, sin_linear, sout_linear, inhibit_flag, y_scratch); |

|Nop(); |

|} |

|for(i = 0; i < FRAME; i++) |

|sout_ulaw[i] = lin2ulaw(sout_linear[i]); |

|} |

This is the while forever loop. DCI_SPI_Done and DCI_Done are variables controlled by an interrupt service routine. The legacy code is frame-based. 80 samples are processed by the routine AcousticEchoCanceller each time it is called. 80 samples corresponds to 10 milli-seconds of data at an 8000 Hz sampling rate.

|void __attribute__((__interrupt__)) _DCIInterrupt(void) |

|{ |

|/* reset DCI interrupt flag */ |

|IFS2bits.DCIIF = 0; |

| |

|toggle_bit = !toggle_bit; |

|LATFbits.LATF1 = toggle_bit; |

| |

|/* In analog mode, for both phone and mic/speaker data bytes */ |

|DCI_Rx[DCI_Index++] = RXBUF0 >> 8; |

|DCI_SPI_Rx[DCI_SPI_Index++] = RXBUF0; |

|DCI_Rx[DCI_Index++] = RXBUF1 >> 8; |

|DCI_SPI_Rx[DCI_SPI_Index++] = RXBUF1; |

|DCI_Rx[DCI_Index++] = RXBUF2 >> 8; |

|DCI_SPI_Rx[DCI_SPI_Index++] = RXBUF2; |

|DCI_Rx[DCI_Index++] = RXBUF3 >> 8; |

|DCI_SPI_Rx[DCI_SPI_Index++] = RXBUF3; |

|DCI_Index -= 4; |

|DCI_SPI_Index -= 4; |

| |

|TXBUF0 = (DCI_Tx[DCI_Index++] ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download