Data acquisition and device control with Scilab.

Dependencies:   Servo MPU6050

Desciription

Scilab is a freeware alternative to MATLAB. For low-cost data acquisition and device control a nice Arduino toolbox is available.

https://os.mbed.com/media/uploads/hudakz/scilab.png

This site presents a Mbed port which allows to use Mbed boards (equipped with Arduino header) rather than Arduino to import real time data into Scilab and to control real equipment witch Scilab.

https://os.mbed.com/media/uploads/hudakz/arduino-temp-read_imagelarge_1.jpeg

Installation
  • Install Scilab to your PC, if not done yet.
  • Launch Scilab and install the Arduino toolbox by executing the following command from the Scilab console:

--> atomsInstall("arduino")
Controlling Mbed's digital output from Scilab
  • In Xcos open examples/Arduino1.zcos

/media/uploads/hudakz/scilab_arduino1.png

  • Double click on the Board setup block and replace the serial port number with mbed's actual virtual serial port number.
  • Double click on the Digital WRITE block and set Digital Pin to 13 (D13 is connected to LED1).
  • Start simulation and LED1 on the Mbed board should start blinking.
Reading and displaying Mbed's analog input
  • In Xcos open examples/Arduino2.zcos

/media/uploads/hudakz/scilab_arduino2.png

  • Double click on the Board setup block and replace the serial port number with mbed's actual virtual serial port number.
  • Double click on the Analog READ block and set Analog Pin to 2.
  • Start simulation and a graph should appear showing the analog signal measured on Mbed's pin A2.

NOTE: Currently, there is bug in the toolbox ARDUINO_ANALOG_READ_sim function (I have reported to Scilab) so the analog readings are not correct.

/media/uploads/hudakz/scilab_graph.png

PID controller
  • In Xcos open examples/Arduino9.zcos

/media/uploads/hudakz/scilab_arduino9.png

Revision:
0:295b7e1c12f3
diff -r 000000000000 -r 295b7e1c12f3 ScilabSerial/ScilabSerial.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ScilabSerial/ScilabSerial.cpp	Mon Jan 18 19:51:22 2021 +0000
@@ -0,0 +1,583 @@
+#include "ScilabSerial.h"
+
+extern UnbufferedSerial dbgPort;
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+ScilabSerial::ScilabSerial(PinName tx, PinName rx, int baud /*= 115200*/ ) :
+    SerialBase(tx, rx, baud),
+    _rxComplete(false),
+    _rxBufLen(0),
+    _rxDataLen(0),
+    _deltaT(0),
+    _mpu6050Available(false)
+{
+    attach(callback(this, &ScilabSerial::onRx));
+
+    // Analog inputs
+    for (size_t i = 0; i < sizeof(_analogPinName) / sizeof(*_analogPinName); i++) {
+        // indexes -> pin names
+        if (_analogPinName[i] != NC)
+            _analogIns[i] = new AnalogIn(_analogPinName[i]);
+        else
+            _analogIns[i] = NULL;
+    }
+
+    // PWM outputs
+    for (size_t i = 0; i < 4; i++) {
+        // indexes 0 to 3 -> pin names D4 to D7
+        if (_digitalPinName[i + 4] != NC)
+            _pwmOuts[i] = new PwmOut(_digitalPinName[i + 4]);
+        else
+            _pwmOuts[i] = NULL;
+    }
+
+    // Digital read write
+    for (size_t i = 0; i < sizeof(_digitalInOuts) / sizeof(*_digitalInOuts); i++)
+        _digitalInOuts[i] = NULL;
+
+    // Servo
+    for (size_t i = 0; i < sizeof(_servos) / sizeof(*_servos); i++)
+        _servos[i] = NULL;
+
+    // MPU6050
+    _mpu6050 = NULL;
+
+    // Interrup-in counters
+    for (size_t i = 0; i < sizeof(_intrInCounters) / sizeof(*_intrInCounters); i++)
+        _intrInCounters[i] = NULL;
+
+    // DC motors
+    for (size_t i = 0; i < sizeof(_dcMotors) / sizeof(*_dcMotors); i++)
+        _dcMotors[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::onRx()
+{
+    // If the '\n' character was used as 'packet-end' delimiter then we could have:
+    //
+    //    while (readable()) {
+    //        volatile char   c = _base_getc();
+    //        if (c != '\n') {
+    //            if (rxBufLen >= RX_MAX)
+    //                rxBufLen = 0;                   // rxBuf overflow
+    //            rxBuf[rxBufLen++] = c;
+    //        }
+    //        else {
+    //            // packet complete
+    //            memset(rxData, 0, RX_MAX);          // clear rxData
+    //            memcpy(rxData, rxBuf, rxBufLen);    // copy rxBuf to rxData
+    //            rxDataLen = rxBufLen;               // set rxData length
+    //            memset(rxBuf, 0, RX_MAX);           // clear rxBuf
+    //            rxBufLen = 0;
+    //        }
+    //    }
+    // Unfortunately there is neither packet length information in the packet
+    // nor 'packet-end' delimiter is used.
+    // That's why we have to figure out the packet length on the fly based on it's content:
+    volatile char   c = _base_getc();       // Read a byte from UART
+
+    _rxBuf[_rxBufLen++] = c;                // Store the byte in the receive buffer
+
+    switch (_rxBuf[0]) {
+        case 'A':                           // Analog read
+        case 'G':                           // MPU6050
+        case 'R':                           // Revision request
+            if (_rxBufLen == 2)
+                _rxComplete = true;
+            break;
+
+        case 'W':                           // PWM (analog) write
+        case 'I':                           // Interrupt-in counter
+            if (_rxBufLen == 3)
+                _rxComplete = true;
+            break;
+
+        case 'D':                           // Digital read/write
+            if (_rxBufLen == 3)
+                if (_rxBuf[1] == 'r')
+                    _rxComplete = true;
+            break;
+
+        case 'S':                           // Servo motor
+            if (_rxBufLen == 3)
+                if (_rxBuf[1] != 'w')
+                    _rxComplete = true;
+            break;
+
+        case 'E':                           // Encoder
+            if (_rxBufLen == 3)
+                if (_rxBuf[1] != 'a')
+                    _rxComplete = true;
+            break;
+
+        case 'C':
+            if (_rxBufLen == 5)
+                _rxComplete = true;
+
+        case 'M':                           // DC motor
+            if (_rxBufLen == 3)
+                if (_rxBuf[1] == 'r')
+                    _rxComplete = true;
+            break;
+    }
+
+    if ((_rxBuf[0] != 'C') && (_rxBufLen == 4))
+        _rxComplete = true;
+
+    if (_rxComplete) {
+        _rxComplete = false;
+        memset(_rxData, 0, RX_MAX);         // reset rxData
+        memcpy(_rxData, _rxBuf, _rxBufLen); // copy rxBuf to rxData
+        _rxDataLen = _rxBufLen;             // set rxData length
+        memset(_rxBuf, 0, RX_MAX);          // reset rxBuf
+        _rxBufLen = 0;
+    }
+
+//    dbgPort.write((void*)(& _rxData), _rxDataLen);
+//    dbgPort.write("\r\n", 2);
+
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+ssize_t ScilabSerial::write(const void* buffer, size_t length)
+{
+    const char*     ptr = (const char*)buffer;
+    const char*     end = ptr + length;
+
+    lock();
+    while (ptr != end) {
+        _base_putc(*ptr++);
+    }
+
+    unlock();
+
+    return ptr - (const char*)buffer;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::run()
+{
+    while (1) {
+        if (_mpu6050Available)
+            mpu6050UpdateData();
+
+        if (_rxDataLen > 0) {
+            _rxDataLen = 0;
+            switch (_rxData[0]) {
+                case 'A':   // Analog read
+                    analogRead();
+                    break;
+
+                case 'W':   // PWM (analog) write
+                    pwmWrite();
+                    break;
+
+                case 'D':   // Digital
+                    digitalReadWrite();
+                    break;
+
+                case 'S':   // Servo motor
+                    servo();
+                    break;
+
+                case 'G':   // MPU6050
+                    mpu6050();
+                    break;
+
+                case 'I':   // Interrupt-in counter
+                    intrInCounter();
+                    break;
+
+                case 'E':   // Encoder
+                    encoder();
+                    break;
+
+                case 'C':   // DC motor init
+                    dcMotorInit();
+                    break;
+
+                case 'M':   // DC motor
+                    dcMotor();
+                    break;
+
+                case 'R':   // Revision
+                    revision();
+                    break;
+            }
+        }
+    }
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::analogRead()
+{
+    int     pin = _rxData[1] - '0';                     // analog pin number 0 .. 5
+    uint8_t i = pin;                                    // index of corresponding analogIn 0 .. 5
+
+    if ((pin >= 0 && pin <= 5) && (_analogIns[i] != NULL)) {
+        uint16_t    word = _analogIns[i]->read_u16();   // get decimal value 0 .. 65535
+        float       val = word / 64.0f;                 // map 16bit range to 10bit range
+
+        word = round(val);
+        write((uint8_t*) &word, sizeof(uint16_t));      // send it to Scilab
+    }
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::pwmWrite()
+{
+    int     pin = _rxData[1] - '0';                 // digital pin number 4 .. 7
+    uint8_t i = pin - 4;                            // index of corresponding analogOut (PWMOut) 0 .. 3
+
+    if ((pin >= 4 && pin <= 7) && (_pwmOuts[i] != NULL)) {
+        uint8_t val = _rxData[2];                   // the duty cycle: between 0 (always off) and 255 (always on)
+        float   dutyCycle = (float)val / 255.0f;    // 0.0f (representing on 0%) and 1.0f (representing on 100%)
+
+        _pwmOuts[i]->write(dutyCycle);
+    }
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::digitalReadWrite()
+{
+    int     pin = _rxData[2] - '0';         // digital pin number 8 .. 13
+    uint8_t i = pin - 8;                    // index of corresponding digitalInOut 0 .. 7
+
+    if ((pin >= 8 && pin <= 13) && (_digitalPinName[pin] != NC)) {
+        char            cmd = _rxData[1];   // command
+        PinDirection    pinDirection;       // pin direction (PIN_INPUT, PIN_OUTPUT)
+        char            c;
+        uint8_t         val;
+
+        switch (cmd) {
+            case 'a':                       // 'a'-> activate
+                pinDirection = (PinDirection) (_rxData[3] - '0');
+                if (_digitalInOuts[i] != NULL)
+                    delete _digitalInOuts[i];
+                _digitalInOuts[i] = new DigitalInOut(_digitalPinName[pin], pinDirection, PullUp, 0);
+                break;
+
+            case 'r':                       // 'r'-> read
+                c = _digitalInOuts[i]->read() + '0';
+                write((uint8_t*) &c, 1);
+                break;
+
+            case 'w':                       // 'w'-> write
+                val = _rxData[3] - '0';
+
+                if (val == 0 || val == 1)
+                    _digitalInOuts[i]->write(val);
+                break;
+        }
+    }
+    else
+        _digitalInOuts[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::servo()
+{
+    int     pin = _rxData[2] - '0' + 1; // digital pin number 2 .. 3
+    uint8_t i = pin - 2;                // index of corresponding servo 0 .. 1
+    uint8_t val;
+
+    if ((pin == 2 || pin == 3) && (_digitalPinName[pin] != NC)) {
+        char    cmd = _rxData[1];
+        switch (cmd) {
+            case 'a':                   // 'a' -> activate servo
+                if (_servos[i] != NULL)
+                    delete _servos[i];
+                _servos[i] = new Servo(_digitalPinName[pin]);
+                break;
+
+            case 'd':                   // 'd'-> delete servo
+                delete _servos[i];
+                _servos[i] = NULL;
+                break;
+
+            case 'w':                   // 'w'-> write to servo position in degree (0 .. 180)
+                val = _rxData[3];
+                if (val >= 0 && val <= 180)
+                    _servos[i]->position(val);
+                break;
+        }
+    }
+    else
+        _servos[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::mpu6050()
+{
+    uint8_t cmd = _rxData[1];
+    uint16_t data[9];
+
+    if (I2C_SCL != NC && I2C_SDA != NC) {
+        switch (cmd) {
+            case 'a':                       // 'a'-> acivate
+                if (_mpu6050 != NULL)
+                    delete _mpu6050;
+                _mpu6050 = new MPU6050();
+                _mpu6050Available = _mpu6050->init();
+                _mpu6050Timer.start();
+                break;
+
+            case 't':                       // 't' -> test
+                if (_mpu6050->selfTestOK()) {
+                    _mpu6050->reset();      // Reset registers to default in preparation for device calibration
+                    _mpu6050->calibrate();  // Calibrate gyro and accelerometers, load biases in bias registers
+                    _mpu6050Available = _mpu6050->init();
+                    _mpu6050Timer.reset();
+                    _mpu6050Timer.start();
+                    write("OK", 2);
+                }
+                else {
+                    _mpu6050Available = false;
+                    write("KO", 2);
+                }
+                break;
+
+            case 'r':                       // 'r'-> read
+                if (_mpu6050Available) {
+                    data[0] = _mpu6050->yaw();
+                    data[1] = _mpu6050->pitch();
+                    data[2] = _mpu6050->roll();
+                    data[3] = _mpu6050->accelX;
+                    data[4] = _mpu6050->accelY;
+                    data[5] = _mpu6050->accelZ;
+                    data[6] = _mpu6050->gyroX;
+                    data[7] = _mpu6050->gyroY;
+                    data[8] = _mpu6050->gyroZ;
+                    write(data, sizeof(data));  // send data to scilab
+                }
+                break;
+        }
+    }
+    else
+        _mpu6050 = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::mpu6050UpdateData()
+{
+    // check if data is available after interrupt
+
+    if (_mpu6050->dataReady()) {
+        _mpu6050->accel();
+        _mpu6050->gyro();
+
+        uint32_t    _now = _mpu6050Timer.read_us();
+        _mpu6050Timer.reset();
+
+        float   _deltaT = float(_now / 1e-6);   // set integration time in seconds
+
+        _mpu6050->madgwickFilter(_deltaT);      // apply filter to estimate yaw, pitch and roll
+    }
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::intrInCounter()
+{
+    int     pin = _rxData[2] - '0'; // digital pin number 8 .. 13
+    uint8_t i = pin - 8;            // index of corresonding IntCounter 0 .. 7
+
+    if ((pin >= 8 && pin <= 13) && (_digitalPinName[pin] != NC)) {
+        uint8_t val;
+        char    cmd = _rxData[1];
+        switch (cmd) {
+            case 'a':               // 'a' -> activate a counter
+                if (_intrInCounters[i] != NULL)
+                    delete _intrInCounters[i];
+                _intrInCounters[i] = new IntrInCounter(_digitalPinName[pin]);
+                break;
+
+            case 'p':               // 'p' -> get position of a counter
+                val = _intrInCounters[i]->read();
+                write(&val, 1);     // send value to scilab (0 .. 256)
+                break;
+
+            case 'r':               // 'r' -> release an interrupt counter
+                delete _intrInCounters[i];
+                break;
+
+            case 'z':               // 'z' -> set to zero (reset) a counter
+                _intrInCounters[i]->reset();
+                break;
+        }
+    }
+    else
+        _intrInCounters[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::encoder()
+{
+    uint8_t i = _rxData[2] - '0';           // encoder index 0 .. 1
+
+    if ((i >= 0 && i <= 1) && (_digitalPinName[i])) {
+        int     pinA, pinB;
+        int8_t  pos;
+        int     mode;
+        char    cmd = _rxData[1];           // command
+
+        switch (cmd) {
+            case 'a':                       // 'a' -> activate an encoder
+                i == 0 ? pinA = 8 : pinA = 10;
+                pinB = pinA + 1;            // pinB -> 9 or 11
+                mode = _rxData[3] - '0';    // '1' -> up chanA, '2' -> up/down chanA, '4' -> up/down chanA and chanB
+                if (_encoders[i] != NULL)
+                    delete _encoders[i];
+                _encoders[i] = new Encoder(_digitalPinName[pinA], _digitalPinName[pinB], mode);
+                break;
+
+            case 'p':                       // 'p' -> get position of an encoder
+                pos = _encoders[i]->position();
+
+                write(&pos, 1);             // send value to scilab (0 .. 256)
+                break;
+
+            case 'r':                       // 'r' -> release an encoder
+                delete _encoders[i];
+                break;
+
+            case 'z':                       // 'z' -> set to zero (reset) the position of an encoder
+                _encoders[i]->reset();
+                break;
+        }
+    }
+    else
+        _encoders[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::dcMotorInit()
+{
+    uint8_t pin1, pin2;
+    uint8_t driverType;
+    uint8_t i = _rxData[1] - '0';               // motor index
+
+    if ((i < 2) && (_digitalPinName[i] != NC)) {
+        pin1 = _rxData[2];                      // pin1 is 2 or 4
+        if (pin1 == 2 || pin1 == 4) {
+            pin2 = _rxData[3] - '0';            // pin2 is 3 or 5
+            if (pin2 == 3 || pin2 == 5) {
+                driverType = _rxData[4] - '0';  //  driver: 0 -> L293, 1 -> L298
+                if (driverType >= 0 && driverType <= 1) {
+                    if (_dcMotors[i] != NULL)
+                        delete _dcMotors[i];
+                    _dcMotors[i] = new DcMotor(_digitalPinName[pin1], _digitalPinName[pin2], driverType);
+                    write("OK", 2);             // tell scilab that motor initialization is complete
+                }
+            }
+        }
+    }
+    else
+        _dcMotors[i] = NULL;
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::dcMotor()
+{
+    int     pin1, pin2;
+    int     mode;
+    char    cmd;
+    float   speed;
+    uint8_t i = _rxData[1] - '0';   // motor index
+
+    if ((i < 2) && (_dcMotors[i] != NULL)) {
+        cmd = _rxData[2] - '0';     // direction of rotation or release
+        switch (cmd) {
+            case 0:                 // clockwise
+            case 1:                 // counter clockwise
+                speed = _rxData[3]; // speed
+                _dcMotors[i]->run(cmd, speed);
+                break;
+
+            case 'r':               // release
+                delete _dcMotors[i];
+                break;
+        }
+    }
+}
+
+/**
+ * @brief
+ * @note
+ * @param
+ * @retval
+ */
+void ScilabSerial::revision()
+{
+    write("v5", 2);
+}