Tuesday, November 26, 2013

Sensor design using MATLAB

Love it or hate it, the Arduino was one of the greatest things to happen in the field of electronics in the past 20 years. It made microcontrollers accessible to everyone, which in turn set off a demand for all sorts of sensors to allow these microcontrollers to take input from the world around them. Nowadays there’s a sensor for almost everything, and you can get them on a module all the required circuitry included - all you have to do is connect the power and data lines! However, just because the connection is easy doesn’t mean the implementation is as well. To design your algorithm, you will need to perform data analysis and simulations, and MATLAB is a tool that can make that much easier for you. In this guide I’ll show some ways to use MATLAB for this through an example with a magnetic vehicle detection sensor.

part 1: sending your data
99% of the time, if you’re talking to a microcontroller it’s going to be over UART. It may be in the form of an FTDI board, USB CDC mode, XBee, RS232, or Bluetooth, but it’s always good old UART. Since it’s primarily used for ASCII data (text, readable stuff), most people just go ahead and send their data over ASCII. It’s easy, but it comes with 2 big drawbacks. Lets say for my sensor, I want to transmit a value for each axis (XYZ) and the number of microseconds since the previous transmission routine. Each one of the axis values is a number between -2048 and 2048, and the microseconds value should ideally be 10000. At the worst case scenario, each line of the data would be:
\r and \n are the carriage return and newline characters. the \ isn’t a separate character, it only specifies that the r and n are not regular printable characters.

That’s 25 characters, which means that you need to transmit 25 bytes of data for each sample. So what if instead of sending a textual representation of the data we send it in raw binary form? Each one of the axis values would be 2 bytes, the timestamp 3, and the \r\n characters 2. That’s only 11 bytes per sample - less than half of the original amount! Plus, this saves the overhead of converting the data to and back from text. There’s also yet another reason why you want to do this when working with Simulink - it doesn’t support strings. You either do it like this or you can’t do it at all.
Speaking of strange restrictions from Simulink, you’re going to have to frame your data rather oddly. A 16 bit integer is 2 bytes and UART sends one byte at a time, so you’d normally send the most significant byte followed by the least significant one, right? Not with Simulink! Lets pack up just the axis values into a frame (along with the newline characters) as an example.
X = -678 0xFD5A 0xFD 0x5A
Y = 1220 0x04C4 0x04 0xC4
Z = -922 0xFC66 0xFC 0x66

Framing that along with the newline characters results in the following 8 byte array
0xFD 0x5A 0x04 0xC4 0xFC 0x66 0x0D 0x0A

That won’t work with Simulink. Instead, you need to frame it like so:
0x5A 0xFD 0xC4 0x04 0x66 0xFC 0x0D 0x0A

If this is confusing, then here’s my sample Arduino code to transmit a single sample over UART.

byte outBuff[] = { 0, 0, 0, 0, 0, 0, '\r', '\n' }; void rawPrintMode() { outBuff[0] = (((sensor.rawVals)[0] & 0x00ff)); outBuff[1] = (((sensor.rawVals)[0] & 0xff00) >> 8); outBuff[2] = (((sensor.rawVals)[1] & 0x00ff)); outBuff[3] = (((sensor.rawVals)[1] & 0xff00) >> 8); outBuff[4] = (((sensor.rawVals)[2] & 0x00ff)); outBuff[5] = (((sensor.rawVals)[2] & 0xff00) >> 8); Serial.write(outBuff, 8); }

But enough about how you should frame your data, let’s go on to Simulink.

part 2: receiving the data
Okay, so we’re transmitting data from our sensor, now we need to receive it.
The model above is our “Hello World” proof of concept. It merely reads in data and displays it through a display and a scope. The configuration block is self explanatory, but the receive block is a little confusing.
Terminator: As you can see in my example Arduino code, all of my frames end with \r\n, so that’s what I selected for the terminator.
Data Type: Is it an int? A byte? A long? Is it signed or unsigned? If you’re sending your data in raw format, then you should already know what type it is.
Data Length: This isn’t the number of bytes in the payload part of the frame, it’s the number of values of the given data type. For example, even though I’m sending 6 bytes, I set the size to 3 because each number consists of 2 bytes.
Enable Blocking Mode: Check this box if you don't like real-time performance
Sample Time: MATLAB doesn't count time in seconds, so I don't have anything solid for what you should put here. Go with a very small number, one that's much smaller than your sensor's sampling rate. Also, make sure your simulation is running for inf time with a fixed step size of auto.

As you can see, our signal is getting routed into 4 places. First, there's a scope and a display so you can observe the data in real time. It also gets sent to the current workspace and gets logged to a file so you can then analyze and test against it. Now that you've captured your data, it's time to disconnect the sensor and start the analysis.

part 3: building the algorithm
We have 3 analog inputs and need to generate a single boolean output. The following scripts and graph demonstrate this process step by step.

vals = [x y z]; t = millis; % Find a good section and zoom in. subplot(321) plot(vals) title('1') % 8375 to 9075 seems good. t = t(8375:9075); vals = [x(8375:9075) y(8375:9075) z(8375:9075)]; subplot(322) plot(vals) title('2') % Now we need to remove each channel's offset. % We do this by taking the average of a silent segment. avgs = [mean(x(950:1150)) mean(y(950:1150)) mean(z(950:1150))] for i = 1:3; vals(:,i) = vals(:,i) - avgs(i); end subplot 323 plot(vals) title('3') % To normalize the three axes, we calculate the magnitude. mag = [] for i = 1:length(vals); mag(i,:) = sqrt(sum(vals(i,:).^2)); end subplot 324 plot(mag) title('4') subplot 325 % Finally, we run it through our parser function detected = [] lastState = 0 for i = 1:length(mag); lastState = detectObject(mag(i), lastState); detected(i) = lastState; end plot([mag,detected' .* 120]) title('5') subplot 326 % And now just the triggers detected = [] lastState = 0 for i = 1:length(mag); lastState = detectObject(mag(i), lastState); detected(i) = lastState; end plot(detected' .* 120) ylim([0,280]) title('6')

Here's the main detection function, which is a simple threshold trigger with hysteresis.

function [ output] = detectObject (magnitude, isOn) OFFpoint = 80 ONpoint = 120 if isOn ~= 0 & magnitude < OFFpoint; output = false elseif isOn == 0 & magnitude > ONpoint output = true else output = isOn end end

part 4: implementing the algorithm
From here on out, it's just a matter of porting your MATLAB functions to your platform. For this
example, the detectObject function would be translated into C and be called every time a new data 
sample is received.