Customizing the Microcontroller

When generating code many parameters are automatically configured or based on default values. These can be modified by specifying them to the microcontroller.

General Parameters

General parameters.

Load the package.

Needs["MicrocontrollerKit`"]

Specify the microcontroller explicitly.

sys=(NonlinearStateSpaceModelFull[Which[x>0,0,True,1], x, x, \[Null], 1, {}Automatic, ]);
mc[pars___]:= <|"Target"->"ArduinoUno","Outputs"->13->"Digital",pars|>;
p="/dev/cu.usbmodem14101";

MicrocontrollerEmbedCode[sys,mc["Microcontroller"->"ATmega328P_28PDIP"],p]

Specify the operating voltage.

MicrocontrollerEmbedCode[sys,mc["OperatingVoltage"->5],p]

Specify the system clock frequency.

MicrocontrollerEmbedCode[sys,mc["ClockFrequency"->16 (10^6)],p]

Timer Parameter

At each sampling instant the microcontroller reads the input signals, updates the states, computes the outputs, and sends the outputs out. When the sampling period is reached, the timer is reset and the process is repeated all over again. During each sampling period the microcontroller is busy for some time and idle for the remaining time, except if it has to deal with any interrupts that are triggered.

(Legended[, (SwatchLegend["busy", "idle"])])

The sampling period can be a critical factor in determining the performance of the system. Choosing the sampling period is usually a tradeoff between performance and power consumption.

The information to set up the microcontoller to sample at each sampling instant can be found in its datasheet. By default, a timer and its parameters are automatically chosen. They can also explicitly set.

Specify the timer and its parameters.

timer={"2","Mode"->"7","Prescaler"->1024,"Top"-> 124};

ℳ=MicrocontrollerEmbedCode[sys,mc["Timer"->timer],p]

The error is the difference between the desired and actual sampling period.

ℳ["Timer"]["2"]["SamplingError"]

Automatically determine the timer.

ℳ=MicrocontrollerEmbedCode[sys,mc[],p]

The parameters when the timer was automatically configured.

ℳ["Timer"]

The following special values for timer can be specified.

Special timer settings.

The settings "delay", "delayMicroseconds", "millis" or "micros" uses functions from the Arduino libraries.  The microcontroller first completes all its tasks, delays for τ seconds, and then starts all over again.

Use the "delay" function from the Arduino library.

ℳ=MicrocontrollerEmbedCode[sys,mc["Timer"->"delay"],p]

The timer parameters are all set as (RefLink[Automatic, paclet:ref/Automatic]).

ℳ["Timer"]

The Arduino libraries to use can be specified if needed.

lib=FileNameJoin[{$RootDirectory,"Applications","Arduino.app","Contents","Java","hardware","arduino","avr","cores"}];

MicrocontrollerEmbedCode[sys,mc["Timer"->"delay"],p,<|"Libraries"->lib|>]

The setting (RefLink[None, paclet:ref/None]) can be used for continuous operation. The new set of inputs are read immediately after the outputs are sent out. This can be used when the sampling period can be safely neglected. The timer is not set up in this case.

Continuously adjust the LED's brightness based on the potentiometer setting at A0.

ℳ=MicrocontrollerEmbedCode[(StateSpaceModelFull[1, 1, , ]),<|"Target"->"ArduinoUno","Inputs"->"A0"->"Analog","Outputs"->3->"Analog","Timer"->None|>,p]

The error in sampling is the sampling period.

ℳ["Timer"][None]["SamplingError"]==1

Shared Parameters

Shared parameters.

In some cases several pins, input, or output channels share a common functionality. For example, the analog to digital conversion is multiplexed among several input pins, or the same serial communication pins are used by several inputs and outputs. The setting in the microcontroller suboptions such as "ADC" and "Serial" will apply to all these channels.

ADC Example

Consider a system that has 3 analog inputs and one output.

A system whose output is the average of 3 inputs.

sys=(StateSpaceModelFull[(1/3), (1/3), (1/3), 1, , ]);

All the 3 inputs use AVCC as the reference value.

Embed code that uses AVCC as reference.

adc1=Table[a->"Analog",{a,{"A0","A1","A2"}}]

adcSpec="ADC"->"Reference"->"AVCC";

This common reference can be specified as a microcontroller suboption.

Embed code that uses the same reference and outputs the average analog value.

ℳ1=MicrocontrollerEmbedCode[sys,<|"Target"->"ArduinoUno","Inputs"->adc1,"Outputs"->3->"Analog",adcSpec|>,p]

It may sometimes be possible to customize the functionality for an individual channel by specifying the setting directly for the channel.

The channel "A2" uses the internal reference value.

adc2=Join[Table[a->"Analog",{a,{"A0","A1"}}],{"A2"-><|"Type"->"Analog","Reference"-> "Internal"|>}]

The remaining channels use the common specification.

ℳ2=MicrocontrollerEmbedCode[sys,<|"Target"->"ArduinoUno","Inputs"->adc2,"Outputs"->3->"Analog",adcSpec|>,p]

This will very likely result in larger code.

(ℳ1["SourceCodeByteCount"]<ℳ2["SourceCodeByteCount"]) &&  (ℳ1["MachineCodeByteCount"]<ℳ2["MachineCodeByteCount"])

SPI Example

Consider a setup where two MAX6675 thermocouples are reading data. They both use the same SPI bus, and the exchange sequences are also the same. However different pins are used to select them as slaves.

The same exchange sequence is used.

ssPins = {"8","9"};

spi=Table[{ss->{"SELECT","READ","READ","DESELECT"}},{ss,{"8","9"}}]

The utility to convert the bytes to a temperature value is also the same.

The C code that converts the incoming bytes to temperature in °C.

util =CFunction["double","convert", {"uint8_t", #}&/@ {"b1", "b2"},  {CDeclare["uint16_t",CAssign["sxtnBtValue",COperator[BitOr,{COperator[BitShiftLeft,{"b1",8}],"b2"}]]],CReturn@COperator[Times,{0.25,COperator[BitShiftRight,{"sxtnBtValue",3}]}]}]//ToCCodeString

The input and output specifications are also the same, except for the slave select pins.

The input specifications.

conversion[b1_,b2_]:=CCall["convert",{b1,b2}]

ic=Table["12"-><|"Type"-> "SPI", "SlaveSelect"->ss,"Conversion"->conversion|>,{ss,ssPins}]

Output specifications.

oc=Table["1"->"Serial",{2}]

Since the utilities for by both thermocouples are the same, it can be given as a common utility code snippet that needs to be executed by the microcontroller.

Embed the code that runs a common utility function for both the inputs.

ℳ=MicrocontrollerEmbedCode[(StateSpaceModelFull[1, 0, 0, 1, 1, , ]),<|"Target"->"ArduinoUno","Inputs"->ic,"Outputs"->oc,"Utilities"->util|>,p,<|"SPI"->spi|>]

Misc Parameters

Some microcontroller boards such as the Arduino Yun rely on being configured as a Communications Device Class (CDC) device to automatically upload new programs on to the board. This functionality also is useful during the operation of the device in cases such as virtual serial communication. With the "CDC" option you may choose to eliminate this functionality to reduce the program footprint and upload new programs by manually resetting the board.

"CDC" parameter values.

Since removing the CDC functionality could result in the board going into a locked state, be sure of the consequences before evaluating the following inputs. If unsure, you can safely evaluate them without physically connecting the Arduino Yun.

Remove the CDC functionality.

sys=(NonlinearStateSpaceModelFull[Which[x>0,0,True,1], x, x, \[Null], 1, {}Automatic, ]);

ℳ1=MicrocontrollerEmbedCode[sys,<|"Target"->"ArduinoYun","Outputs"->"13"->"Digital","CDC"->None|>,p]

Quickly press the reset button twice and upload again.

ℳ2=MicrocontrollerEmbedCode[sys,<|"Target"->"ArduinoYun","Outputs"->"13"->"Digital"|>,p]

With the CDC functionality the code footprint is larger.

{ℳ1["MachineCodeByteCount"],ℳ2["MachineCodeByteCount"]}

The larger footprint comes primarily from the additional files.

tempDir=CreateDirectory[];

MicrocontrollerEmbedCode[sys,<|"Target"->"ArduinoYun","Outputs"->"13"->"Digital"|>,p,<|"WorkingDirectory"->tempDir,"CleanIntermediate"->False|>];

FileNameTake/@FileNames["*.c",tempDir]

Delete the temporary directory and all intermediate files.

DeleteDirectory[tempDir,DeleteContents->True]