Main Content

Design Microcontroller Using the I2C Communication Protocol

This example shows how to design, implement, and test a microcontroller driver to configure and read magnetometer data using the I2C communications protocol. This example progresses through a model-based design cycle, from requirements to code generation.

Open the Project

Open the project from the command line.

i2cstateflow = openProject("i2cstateflow");
addPath(i2cstateflow,"tests/testBaselines");

Opening the project also opens the top model.

Model architecture and conventions

The top model consists of three major components:

  1. Compiler provided functions: Assume that the compiler for the microcontroller provides a set of high-level functions to interact with the I2C peripheral. Use Simulink functions to interact with the magnetometer reference subsystem.

  2. Magnetometer: This reference subsystem implements a state machine that replicates the behavior of an I2C peripheral, particularly a magnetometer.

  3. Microcontroller: This reference model implements the necessary state machine in a Stateflow chart for the peripheral configuration and continuous operation to receive magnetometer readings via I2C.

The microcontroller implements the I2C registers as global data stores via Simulink.Signal objects in the base workspace.

The magnetometer sensor is the HMC5983 Honeywell 3-Axis Digital Compass. The microcontroller is modeled after the Microchip Digital Signal Controller for the dsPIC 33F family.

Simulate the Example

In this example, you can use the Project Shortcuts tab to quickly access Requirements, Modeling, Testing, and Code Generation.

Requirements Review

Requirements Toolbox lets you author, analyze, and manage requirements. As a starting point for this example, review twenty eight functional requirements defined for the I2C driver implementation. To open the requirements set, click the Requirements Review shortcut in the project shortcut bar.

These requirements are in two groups:

  1. Device Configuration: These requirements describe the requirements for the magnetometer configuration.

  2. Device Operation: These requirements describe how the model must continuously receive the magnetometer readings.

Model Design

To open the top model, click the Open Top Model shortcut in the Project Shortcuts tab. The two key components of the model are the magnetometer and the microcontroller I2C driver.

Magnetometer Model

In the top model, open the Magnetometer subsystem to see the magnetometer model. The magnetometer mimics the I2C transactions, as these would happen when interacting with the physical device. It is important to note that this is a logical implementation; this model aims to respond to events in the I2C bus and generate I2C interrupts, which are represented by function calls, instead of representing the actual states of the SDA and SCL lines.

The Magnetometer chart receives I2C events in the form of messages. Depending on the current state of the I2C transaction and the microcontroller, the magnetometer generates an I2C function call, a Data Ready function call, or both.

Microcontroller Model

In the top model, open the uController subsystem to see the microcontroller model. The microcontroller model is meant to be used for both, simulation and deployed in a microcontroller as an I2C driver. The objective is for it to be designed and tested, first in simulation, then in Software-In-the-Loop (SIL), and finally generate code for microcontroller deployment.

The event-triggered Driver chart wakes up for every I2C or Data Ready event. At the end of a successful I2C transaction, the readingReady outport is 1 and the magReading port contains the 6 bytes corresponding to 2 bytes for each of the X, Z, and Y magnetometer reading. The chart makes use of functions provided by the microcontroller compiler to interact with the I2C peripheral.

The Driver chart has two parent states: Startup and Reading. While the Startup state is active, the chart goes through the process of configuring the magnetometer registers as established in the requirements. Once the configuration is done, the Reading state becomes active. Every time the chart receives a Data Ready (DRDY) event, it starts a new I2C reading transaction with the magnetometer to get the new reading.

The chart makes use of variant transitions to optionally incorporate error handling when there are bus collisions in the I2C bus.

Examine Links Between Requirements and Model

You can use the Requirements Toolbox to link each individual requirement to the Simulink model component that implements that requirement. Open the i2cStateflowMicrocontroller model.

open_system('i2cStateflowMicrocontroller')

After the model opens, enter the Requirements Perspective by clicking in the Show Perspectives views button in the lower-right corner of the model canvas.

Then click Requirements.

In the Requirements perspective, open the Stateflow chart to see how some of the requirements are implemented.

open_system('i2cStateflowMicrocontroller/Driver')

The shaded text boxes show the requirement links between the chart elements and the requirements they implement.

Alternatively, you can navigate from a listed requirement to its implementation. Open the Requirements Editor by clicking the Requirements Review shortcut in the Project Shortcut tab.

Select a functional requirement and in the right pane, expand the Links section. The Implemented by section shows the model elements that implement the requirement.

For example, for the above selected requirement, clicking the Startup link takes you to where this requirement is implemented in the Driver chart.

Run the Model

Reopen the top simulation model by clicking the Open Top Model shortcut in the Project Shortcut tab. Click Run on the toolstrip. The model runs and complete a few I2C transactions.

To show how the state machine progresses through the transaction:

  1. Open the Driver chart in the i2cMicrocontroller model.

  2. In the toolstrip, from the Debug tab, display the Animation Speed dropdown menu, and select Slow.

  3. Run the model.

The model steps through the chart as the peripheral configuration occurs and receives magnetometer readings.

Another way to view how the I2C transaction occur, is to open the Sequence Viewer at the top model level. In the top model, in the Simulation tab, click Sequence Viewer to open a sequence diagram that shows how the events occur in the simulation. For example, the image below shows the first few interactions between the magnetometer and the microcontroller when performing a sensor reading.

Error Handling

The I2C protocol defines multiple error conditions that can happen during a transaction. This example models two of such errors: a write collision error and an overwrite error. The write collision error happens when two devices try to write at the same time in the bus. This typically happens when two devices try to initiate a transaction at the exact same time. The overwrite error happens when a peripheral sends a byte to the controller device before the controller has had the chance to read the previous byte and thus the read buffer is overwritten.

To see what can happen when there is no error handling, disable the error handling variant transitions in the Driver chart and enable the block that creates the error conditions.

ERRHDL.Value = false;
ERRGEN.Value = true;

Run the simulation. After the successfully receiving the first magnetometer reading (readingReady), an overwrite error occurs (I2COV) and the device never recovers from this error condition and does not receive any other readings.

errorHandlingFalse.png

Now enable the error handling for variant transitions.

ERRHDL.Value = true;

With error handling enabled, the model responds by recovering from error once it is cleared and continues receiving sensor readings.

errorHandlingTrue.png

Disable error handling.

ERRHDL.Value = false;
ERRGEN.Value = false;

Testing the Model

This example includes a test harness and a Simulink Test test suite that tests the behavior of the Driver chart.

Test Harness

The test harness is an independent model that progresses the chart through the Startup and Reading states and verifies that the chart makes the correct I2C functions calls as it conducts I2C transactions.

To open the test harness, click the Open Test Harness shortcut in the Project Shortcut tab.

TestHarness.png

The Test Sequence block contains eight different scenarios. Seven of the scenarios progressively test more sections of the Driver chart. The first scenario tests a complete sequence from power up device configuration all the way to get a complete sensor reading. Each scenario uses verify statements to ensure that the chart progresses as expected.

Test Suite

This example includes eight tests that automate testing the model to verify each of the requirements are met. To see how these tests are implemented, click the Open Test Suite shortcut in the Project Shortcut tab. This opens the test suite in the Test Manager. Each test is responsible to verify a subset of the requirements are met by running the test harness on a given Test Sequence scenario. If all verify statements are verified, the test passes.

To run the complete test suite, select i2cMagnetometerDriverTests in the test browser, and click the Run. All tests pass.

Requirement Validation

As a final step in testing the model, open the Requirement Editor by clicking the Requirements Review project shortcut and select Implementation Status and Validation Status from the toolstrip.

All requirements are implemented and verified by Simulink Test.

Generate Code

Next, generate code for the Driver chart to deploy to an embedded system.

  1. Open the microcontroller model. Click the Open uC model link in the Project Shortcuts tab.

  2. In the Apps tab, click Embedded Coder.

  3. In the C Code tab, click Generate Code.

The generated code has 3 entry point functions.

  1. void i2cStateflowMicrocontroller_initialize(void): This function initializes the necessary variables and the state machine. This function should be called once after device startup.

  2. void DRDY(void) and void i2cEvent(void): Call these functions when either an I2C or a Data Ready interrupts are triggered

See Also

(Simulink)