Skip navigation

IoT on Wheels Design Challenge

15 Posts authored by: fyaocn

1. With all the effort in 11 weeks, there is little surprise for the completion of this design by Nov. 13,2017.

The software and hardware have passed test. Only need some platform to carry all the electronic parts and driving the wheels.

Getting close view.

Rear view with wheels. There have been too many wheels.

2. All the parts are make by recycling parts like carton package and plastic case for Nucleo board. Wiring with jumper lines.

It works, but far from perfect.  I will try to upgrade the design and enclosed them for better function.

 

3. Conclusion

It is great to use MBED OS to complete the design. This gives me chances to explore every parts used in this design. Even prototype as toy, this glider can be real product with higher power output and compact electronic design. There have been many drone solution with STM32F1xxx. Obvious STM32L476 can do very well too.

With BlueNRG, BLE turns out to be little problem throughout the process, it proves to be reliable choice for BLE communication.

1. Although the software has been completed there are still some problems with power supply.

I supply power with battery via USB. The st-link part works and the main part with STM32L476 chip does not.

It appears that  only PWR_EN is needed to enable the USB power. But it is not the case for Nucleo-F401 after test.

The power supply is provided either by the host PC through the USB cable, or by an external source: VIN (7V-12V), E5V (5V) or +3.3V power supply pins on CN6 or CN7.

Then I change to VIN. There are three external power input, VIN, E5V, +3.3V. For VIN, 7-12V is required. In my hardware test, 5.25V works fine. Since the specification of LD1117 can handle such power input.

The +3.3V and VIN is accessible in arduino compatible connector CN6, But E5V is only accessible in Morpho connector CN7.

2. Another problem comes that PWM can not drive the L298N even the code is OK. I think the PWM can not response to PWM in higher frequency. Make the switch frequency slower can solve the problem.

I use digitalwrite to make it work and change the Switch Part as follows,

DigitalOut motorA(A5, 0);
DigitalOut motorB(A4, 0);

... ...

 switch (flycontrol)
    {
        case 0:         // Stop
            pwml=0;
            pwmr=0;
            motorA=0;
            motorB=0;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 1:         //Fly
            pwml=PWM_FLY;
            pwmr=PWM_FLY;
            motorA=1;
            motorB=1;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 2:         //turn left
            pwml=pwml-PWM_DELTA;
            pwmr=pwmr+PWM_DELTA;
            motorA=0;
            motorB=1;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case '3':         //turn right
            pwml=pwml+PWM_DELTA;
            pwmr=pwmr-PWM_DELTA;
            motorA=1;
            motorB=0;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 4:         //Speed up
            pwml=pwml+PWM_DELTA;
            pwmr=pwmr+PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 6:         //Speed down
            pwml=pwml-PWM_DELTA;
            pwmr=pwmr-PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        default: // Nothing change if not 1~6
            pwml=pwml;
            pwmr=pwmr;
        break;
     }

 

3. Then it works fine. I can control both motors from mobile. The rest work is mechanical part.  I will make it in last blog.

1. Finally all the software part is completed. It is obvious that PWM is most tricky part, since I do not know when it starts without and clear entry point.

2. The Software in mbed.org first the flyservice.h

#ifndef __BLE_FLY_SERVICE_H__
#define __BLE_FLY_SERVICE_H__
class FLYService {
public:
    const static uint16_t FLY_SERVICE_UUID              = 0xA000;
    const static uint16_t FLY_STATE_CHARACTERISTIC_UUID = 0xA001;

    FLYService(BLEDevice &_ble, bool initialValueForFLYCharacteristic) :
        ble(_ble), flyState(FLY_STATE_CHARACTERISTIC_UUID, &initialValueForFLYCharacteristic)
    {
        GattCharacteristic *charTable[] = {&flyState};
        GattService         flyService(FLY_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
        ble.addService(flyService);
    }
    GattAttribute::Handle_t getValueHandle() const {
        return flyState.getValueHandle();
    }

private:
    BLEDevice                         &ble;
    ReadWriteGattCharacteristic  flyState;
};
#endif /* #ifndef __BLE_FLY_SERVICE_H__ */

Then the main.c

#include "mbed.h"
#include "ble/BLE.h"
#include "FlyService.h"
#define MOTOR_PERIOD 1 // 1ms in PWM period
#define PWM_FLY 50     // PWM start fly value in DC%
#define PWM_DELTA 1 // PWM Delta value in DC%
void initFlyWheels(void);
void flyWheelsInstance(int flycontrol);
PwmOut motorl(PA_0);
PwmOut motorr(PA_1);
uint16_t pwml,pwmr; // PWM value in DC% ranging 0~100
DigitalOut actuatedLED(LED1, 0);
Serial pc(USBTX, USBRX);

const static char     DEVICE_NAME[] = "FLY";
static const uint16_t uuid16_list[] = {FLYService::FLY_SERVICE_UUID};

FLYService *flyServicePtr;

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    (void)params;
    BLE::Instance().gap().startAdvertising(); // restart advertising
}

/**
 * This callback allows the FLYService to receive updates to the ledState Characteristic.
 *
 * @param[in] params
 *     Information about the characterisitc being updated.
 */
void onDataWrittenCallback(const GattWriteCallbackParams *params) {
    if ((params->handle == flyServicePtr->getValueHandle()) && (params->len == 1)) {
        actuatedLED = *(params->data);
        //pc.printf("\r\n%d\r\n",*(params->data));
        flyWheelsInstance(*(params->data));
    }    
}

/** 
 * This function is called when the ble initialization process has failled 
 */ 
void onBleInitError(BLE &ble, ble_error_t error) 
{ 
    /* Initialization error handling should go here */ 
} 


/** 
 * Callback triggered when the ble initialization process has finished 
 */ 
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) 
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;
        
    initFlyWheels();
    pc.printf("\r\nStarting ... ...\r\n");


    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }


    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }


    ble.gap().onDisconnection(disconnectionCallback);
    ble.gattServer().onDataWritten(onDataWrittenCallback);


    bool initialValueForFLYCharacteristic = true;
    flyServicePtr = new FLYService(ble, initialValueForFLYCharacteristic);


    /* setup advertising */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
    ble.gap().startAdvertising();


    while (true) {
        ble.waitForEvent();
    }
}


void initFlyWheels()
{
    motorl.period_ms(MOTOR_PERIOD );
    motorr.period_ms(MOTOR_PERIOD );
    // Stop the motor as DC=0
    pwml=0;
    pwmr=0;
    motorl=pwml/100;
    motorr=pwmr/100; 
     
    
}
    
void flyWheelsInstance(int flycontrol)
{
    //flycontrol = 1;


    pc.printf("\r\nflycontrol starting =%d\r\n",flycontrol);
    switch (flycontrol)
    {
        case 0:         // Stop
            pwml=0;
            pwmr=0;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 1:         //Fly
            pwml=PWM_FLY;
            pwmr=PWM_FLY;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 2:         //turn left
            pwml=pwml-PWM_DELTA;
            pwmr=pwmr+PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case '3':         //turn right
            pwml=pwml+PWM_DELTA;
            pwmr=pwmr-PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 4:         //Speed up
            pwml=pwml+PWM_DELTA;
            pwmr=pwmr+PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        case 6:         //Speed down
            pwml=pwml-PWM_DELTA;
            pwmr=pwmr-PWM_DELTA;
            pc.printf("\r\nflycontrol=%d\r\n",flycontrol);
            break;
        default: // Nothing change if not 1~6
            pwml=pwml;
            pwmr=pwmr;
        break;
     }
    
    
    // Setting the pwm control for left motor and right motor in DC%
    pc.printf("\r\npwml=%d--pwmr=%d\r\n",pwml,pwmr);
    motorl=pwml/100;
    motorr=pwmr/100;
}
    
int main(void)
{
    BLE &ble = BLE::Instance();
    ble.init(bleInitComplete);
}

The instance response to number 1 to 6 for different reaction, led to PWM duty cycle variation in pwml and pwmr. While these is still some flaw, the number shall be limited to 0-100. I can fix later with if-then or just simple math function.

3. Here is screenshot for test result .

I used standard Serial communication in 9600bps. Code like printf() can be remarked in final version.

It work well with each number.

5. Another part is APP control, codes as attached zip files. I can not upload the barecode. So check it in whole. Also, android apk file build by cordova is the zip file too.

Screen shot as follows,

Optional, you can try text input directly. Same like button press.

 

Here is the output for cordova build process

6. Hopefully, next bolg shall be the last one, when I install the boards on board. Funny part of the design.

I have made with packing box from Element14 instead of the demo toy at hand.

1. Having Tested all the software components, it is time to do hardware configuration.

- N30 motor, 3-6V, 220mA and 20900rpm in 3.7V, maximum 1.5A, shaft diameter 1mm, length of shaft 6mm, size 20*10*12mm, 8.39g,

- Propeller , diameter 75mm, center hole 0.95mm, 1.6g

- 5-6V Battery Banks, it is idea to use portable battery bank for mobile 5V output, 4xAAA battery as optional

- L298N H bridge driver, Logical-1: 2.3V~Vxx, Logical-0: -0.3V~1.5V. Peak current 2A.

 

2. Parameter design is key issues. The L298 is an integrated monolithic circuit in a 15-lead Multi watt and Power SO20 packages. It is a high voltage, high current dual full-bridge driver designed to accept standard TTL logic levels and drive inductive loads such as relays, solenoids, DC and stepping motors. Two enable inputs are provided to enable or disable the device independently of the in-put signals. The emitters of the lower transistors of each bridge are connected together and the corre-sponding external terminal can be used for the connection of an external sensing resistor. An additional supply input is provided so that the logic works at a lower voltage.

Principle H bridge is as follows,

The DC motor can be controlled with pole-shift

For the L298N, there are two power source, on 5-12V for power drive and 5V for Board drive. Control signal can switch On/Off the CMOS. I use one Portable Mobile Battery Charger as power source,with output 5V. It can control drive the N30 motor in proper direction.

The N30 can offer 30g-60g thrust power, optimized  thrust-weight ratio for glider is about 10-15, unlike that of 8-10 for air-plane. That means maximum weight of real glider shall be 300-500g.

With reducer and larger propeller 60g thrust can be reached by N30 motor. But not for direct shaft coupling propeller.

3. The L298N driver can make the N30 work. As follows,

Power off,

Power on,

The N30 runs if the control pin is set to 1-0.

Here is control sequence for 4 pin order

0-0-0-0 or 1-1-1-1 , stop

1-0-1-0, turn forward Motor A and Motor B

0-1-0-1, turn backward Motor A and Motor B,

4. The digital out of 0-1 can control the direction. With PWM output, speed can be  controlled, and different PMW in two motor can change the left or right of the FlyWheels.

1. I have tried many times to understand PWM function in mbed this week. Only to blink the LED by PWM in 60Hz, which turning to be very simple. As follows,

#include "mbed.h"
PwmOut mypwm(LED1);
int main() {
    mypwm.period_ms(1000);
    mypwm=0.2;
   while(1) { }
}

2. PwmOut function packs many process of PWM output from clock setting, interrupt triggering. But all these is transparent to developers. It takes me long time to find out that the PWM starts when PinName is assigned to PwmOut. It runs on 50Hz and 0.5 Duty circle.

The rest codes only redefine the parameters.

Furthermore, the following two lines are equal in results.

               mypwm=0.2;

and          mypwm.write(0.2);

3. It is too easy to use but it takes time to find out what happens.

1. To control the via BLE, the BLE service and BLE characteristics UUID shall be nominated.

In the mbed projects, 0xA000 plus blue UUID default value -0000-1000-8000-00805f9b34fb shall be used.

And set BLE characteristics UUID to 0xA001-0000-1000-8000-00805f9b34fb.

Device name of FLY. With 1 received to LED on and 0 for LED off.as of

    if ((params->handle == ledServicePtr->getValueHandle()) && (params->len == 1)) {
        actuatedLED = *(params->data);

 

Full codes as,

#include "mbed.h"
#include "ble/BLE.h"
#include "LEDService.h"
DigitalOut actuatedLED(LED1, 0);
const static char     DEVICE_NAME[] = "FLY";
static const uint16_t uuid16_list[] = {LEDService::LED_SERVICE_UUID};
LEDService *ledServicePtr;
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    (void)params;
    BLE::Instance().gap().startAdvertising(); // restart advertising
}
/**
 * This callback allows the LEDService to receive updates to the ledState Characteristic.
 *
 * @param[in] params
 *     Information about the characterisitc being updated.
 */
void onDataWrittenCallback(const GattWriteCallbackParams *params) {
    if ((params->handle == ledServicePtr->getValueHandle()) && (params->len == 1)) {
        actuatedLED = *(params->data);
    }
}
/** 
 * This function is called when the ble initialization process has failled 
 */ 
void onBleInitError(BLE &ble, ble_error_t error) 
{ 
    /* Initialization error handling should go here */ 
} 
/** 
 * Callback triggered when the ble initialization process has finished 
 */ 
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) 
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;
    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }

    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }

    ble.gap().onDisconnection(disconnectionCallback);
    ble.gattServer().onDataWritten(onDataWrittenCallback);
    bool initialValueForLEDCharacteristic = true;
    ledServicePtr = new LEDService(ble, initialValueForLEDCharacteristic);
    /* setup advertising */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
    ble.gap().startAdvertising();
   while (true) {
        ble.waitForEvent();
    }
}


int main(void)
{
    BLE &ble = BLE::Instance();
    ble.init(bleInitComplete);
}

 

2. Now create empty cordova project with

- cordova create flywheels

- cordova platform add android

in www directory, put index.html as control UI as,

 

Then,

- cordova build android

- cordova run

I got apk file and here is the screenshot.

Upload one web UI for this flywheel introduction,

1. As I have mentioned, the X-NUCLEO-IDB04A1 is broken, I guess that the tight socket makes me pull hard to break lines by mistake. I shall take care later on.

2 Luckly  X-NUCLEO-IDB05A1X-NUCLEO-IDB05A1 is still available for me to complete the BLE connection Here is the sample code form mbed

 


#include "mbed.h"
#include "ble/BLE.h"
#include "LEDService.h"


DigitalOut actuatedLED(LED1, 0);


const static char     DEVICE_NAME[] = "LED";
static const uint16_t uuid16_list[] = {LEDService::LED_SERVICE_UUID};


LEDService *ledServicePtr;


void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    (void)params;
    BLE::Instance().gap().startAdvertising(); // restart advertising
}


/**
 * This callback allows the LEDService to receive updates to the ledState Characteristic.
 *
 * @param[in] params
 *     Information about the characterisitc being updated.
 */
void onDataWrittenCallback(const GattWriteCallbackParams *params) {
    if ((params->handle == ledServicePtr->getValueHandle()) && (params->len == 1)) {
        actuatedLED = *(params->data);
    }
}


/** 
 * This function is called when the ble initialization process has failled 
 */ 
void onBleInitError(BLE &ble, ble_error_t error) 
{ 
    /* Initialization error handling should go here */ 
} 


/** 
 * Callback triggered when the ble initialization process has finished 
 */ 
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) 
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;


    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }


    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }


    ble.gap().onDisconnection(disconnectionCallback);
    ble.gattServer().onDataWritten(onDataWrittenCallback);


    bool initialValueForLEDCharacteristic = true;
    ledServicePtr = new LEDService(ble, initialValueForLEDCharacteristic);


    /* setup advertising */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
    ble.gap().startAdvertising();


    while (true) {
        ble.waitForEvent();
    }
}


int main(void)
{
    BLE &ble = BLE::Instance();
    
    ble.init(bleInitComplete);
}

3. With Lightblue I can find the LED equiment and control from the console with 1 or 0 input to make the LED on or off

4. This is a simple test, therefore there is little to modify. The BLE service UUID is 0xA000 and BLE characteristic UUID is 0xA001. I can explore further with the design now.

1. Present progress,

I have tried to test the BlueNRG extension board. But come to find out the board is broke by mistake. It can not work. At the beginning of the design , I have flashed program and test with BLE scanning APP, everything goes fine. I have to skip the repair mode to next stage, I will figure out what happened later.

For Remote control part, WIFI via cloud is challenge part as option work. I think I should use this part now.

2 Another work is migration of X-NUCLEO-IKS01A1 sample by Import library of  X-NUCLEO-IKS01A2X-NUCLEO-IKS01A2 Again I can not acess undisclosed part of code in mbed with brief introduction only

But in the end, I made it.  Full code as follows,


/* Includes */
#include "mbed.h"
#include "XNucleoIKS01A2.h"


/* Instantiate the expansion board */
static XNucleoIKS01A2 *mems_expansion_board = XNucleoIKS01A2::instance();


/* Retrieve the composing elements of the expansion board */
static GyroSensor *gyroscope = mems_expansion_board->acc_gyro;
static MotionSensor *accelerometer = mems_expansion_board->accelerometer;
static MagneticSensor *magnetometer = mems_expansion_board->magnetometer;
static HumiditySensor *humidity_sensor = mems_expansion_board->ht_sensor;
static PressureSensor *pressure_sensor = mems_expansion_board->pt_sensor;
static TempSensor *temp_sensor1 = mems_expansion_board->ht_sensor;
static TempSensor *temp_sensor2 = mems_expansion_board->pt_sensor;


/* Helper function for printing floats & doubles */
static char *printDouble(char* str, double v, int decimalDigits=2)
{
  int i = 1;
  int intPart, fractPart;
  int len;
  char *ptr;


  /* prepare decimal digits multiplicator */
  for (;decimalDigits!=0; i*=10, decimalDigits--);


  /* calculate integer & fractinal parts */
  intPart = (int)v;
  fractPart = (int)((v-(double)(int)v)*i);


  /* fill in integer part */
  sprintf(str, "%i.", intPart);


  /* prepare fill in of fractional part */
  len = strlen(str);
  ptr = &str[len];


  /* fill in leading fractional zeros */
  for (i/=10;i>1; i/=10, ptr++) {
    if(fractPart >= i) break;
    *ptr = '0';
  }


  /* fill in (rest of) fractional part */
  sprintf(ptr, "%i", fractPart);


  return str;
}




/* Simple main function */
int main() {
  uint8_t id;
  float value1, value2;
  char buffer1[32], buffer2[32];
  int32_t axes[3];
  
  printf("\r\n--- Starting new run ---\r\n");


  humidity_sensor->read_id(&id);
  printf("HTS221  humidity & temperature    = 0x%X\r\n", id);
  pressure_sensor->read_id(&id);
  printf("LPS25H  pressure & temperature    = 0x%X\r\n", id);
  magnetometer->read_id(&id);
  printf("LIS3MDL magnetometer              = 0x%X\r\n", id);
  gyroscope->read_id(&id);
  printf("LSM6DS0 accelerometer & gyroscope = 0x%X\r\n", id);
  
  wait(3);

  while(1) {
    printf("\r\n");


    temp_sensor1->get_temperature(&value1);
    humidity_sensor->get_humidity(&value2);
    printf("HTS221: [temp] %7s°C,   [hum] %s%%\r\n", printDouble(buffer1, value1), printDouble(buffer2, value2));
    
    temp_sensor2->get_fahrenheit(&value1);
    pressure_sensor->get_pressure(&value2);
    printf("LPS25H: [temp] %7s°F, [press] %smbar\r\n", printDouble(buffer1, value1), printDouble(buffer2, value2));


    printf("---\r\n");


    magnetometer->get_m_axes(axes);
    printf("LIS3MDL [mag/mgauss]:  %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    accelerometer->get_x_axes(axes);
    printf("LSM6DS0 [acc/mg]:      %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    gyroscope->get_g_axes(axes);3
    printf("LSM6DS0 [gyro/mdps]:   %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    wait(1.5);
  }
}

 

3. First, declare the instance,

static XNucleoIKS01A2 *mems_expansion_board = XNucleoIKS01A2::instance();

Even if you are not familiar with hardware of pins, you can still make it by declare an empty instance(). If it is NULL, D14 and D15 shall be declared as I2C port for sensors.

Then declare variables pointers for sensor data,

/* Retrieve the composing elements of the expansion board */
static GyroSensor *gyroscope = mems_expansion_board->acc_gyro;
static MotionSensor *accelerometer = mems_expansion_board->accelerometer;
static MagneticSensor *magnetometer = mems_expansion_board->magnetometer;
static HumiditySensor *humidity_sensor = mems_expansion_board->ht_sensor;
static PressureSensor *pressure_sensor = mems_expansion_board->pt_sensor;
static TempSensor *temp_sensor1 = mems_expansion_board->ht_sensor;
static TempSensor *temp_sensor2 = mems_expansion_board->pt_sensor;

 

Then I got the full feedback list for sensors, unlike last test in  #4 Coding on Nucleo-STM32L476 with MEMS board X-NUCLEO-IKS01A2 , only similar sensor of temperature is shown,

But the form of data is still not fixed. I shall adjust them one by one.

1. After test the software component for this design, the mechanic part for the walking wheel shall be prepared and tested.

2. I have made wooden frame walking wheel plane driven by 5V motor and one propeller. Here are parts of the components.

3. The front wheels and rear wheels are passive wheels. The wooden wings shall keep the balance only. Assemble the parts as finally,

4. Test the two pins motor with positive of negative for rotary direction of the motor. Change the direction shall make the wooden plane roll forward or backward.

In the prototype walking plane, I shall arrange the Nucleo Boards to control the on/off state and speed of the motor. BLE communication with BLUENRG shall be included as well.

1. After setting of BLE connection, another part of work is PWM-controlled motor for propellers. This is especially easy with mbed.

Since the PWM is embedded in mbedos system, include "mbed.h" first.

#include "mbed.h"

Then create one PWM handle, set the I/O pin. This pin can be in input and output state at the same time. pwm.write() and pwm.read() are available. This demo shows how to blink LED1 as of PWM siganls.

 

DigitalOut  my_led(LED1);
InterruptIn my_button(USER_BUTTON);
PwmOut      my_pwm(PB_3);

... ...
    // Set PWM
    my_pwm.period_ms(10);
    my_pwm.write(0.5);


Pressing the bottom , the square wave form is changed accordingly.

 

    if (my_pwm.read() == 0.25) {
        my_pwm.write(0.75);
    }
    else {
        my_pwm.write(0.25);
    }

 

2. Full code is,

#include "mbed.h"


DigitalOut  my_led(LED1);
InterruptIn my_button(USER_BUTTON);
PwmOut      my_pwm(PB_3);


void pressed() {
    if (my_pwm.read() == 0.25) {
        my_pwm.write(0.75);
    }
    else {
        my_pwm.write(0.25);
    }
}


int main()
{
    // Set PWM
    my_pwm.period_ms(10);
    my_pwm.write(0.5);
    
    // Set button
    my_button.fall(&pressed);
    
    while (1) {
        my_led = !my_led;
        wait(0.5); // 500 ms
    }
}

 

3. Herein, I would show what normal IDE would do,

 

int main(void)
{

  /* USER CODE BEGIN 1 */
        int i=100;
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* System interrupt init*/
  /* Sets the priority grouping field */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_0);
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM10_Init(1000,100);//PWM 

  /* USER CODE BEGIN 2 */
        HAL_TIM_PWM_Start(&htim10,TIM_CHANNEL_1);//PWM
  /* USER CODE END 2 */

  /* USER CODE BEGIN 3 */
  /* Infinite loop */
  while (1)
  {
                if(!HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))//button pressed
                {
                        i=i+100;
                        if(i==1000) i=100;
                        HAL_TIM_PWM_Stop(&htim10, TIM_CHANNEL_1);//PW间为iM
                        MX_TIM10_Init(1000,i);//PWM reset,
                        HAL_TIM_PWM_Start(&htim10,TIM_CHANNEL_1);//PWM
                        while(!HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13));
                }
  }
  /* USER CODE END 3 */

}

Then, first, start hardware, then config the timer, setting of interruption,   then PMW can be started and control by botton.

There would be more to think about, and more control over your hardware.

STM32L476 provide clock source like real time clock(RTC), watchdog clock(WTC), low power time(LPTMx), etc. but in mbed, you can see nothing in regarding to the selection of timer.

4. While, up to now, mbed is good enough for my project.

1. Create new project, using BLUENRG template.

2. Codes and sending commands

#include "mbed.h"
#include "ble/BLE.h"
#include "LEDService.h"


DigitalOut actuatedLED(LED1, 0);


const static char     DEVICE_NAME[] = "LED";
static const uint16_t uuid16_list[] = {LEDService::LED_SERVICE_UUID};


LEDService *ledServicePtr;


void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    (void)params;
    BLE::Instance().gap().startAdvertising(); // restart advertising
}


/**
 * This callback allows the LEDService to receive updates to the ledState Characteristic.
 *
 * @param[in] params
 *     Information about the characterisitc being updated.
 */
void onDataWrittenCallback(const GattWriteCallbackParams *params) {
    if ((params->handle == ledServicePtr->getValueHandle()) && (params->len == 1)) {
        actuatedLED = *(params->data);
    }
}


/** 
 * This function is called when the ble initialization process has failled 
 */ 
void onBleInitError(BLE &ble, ble_error_t error) 
{ 
    /* Initialization error handling should go here */ 
} 


/** 
 * Callback triggered when the ble initialization process has finished 
 */ 
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) 
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;


    if (error != BLE_ERROR_NONE) {
        /* In case of error, forward the error handling to onBleInitError */
        onBleInitError(ble, error);
        return;
    }


    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }


    ble.gap().onDisconnection(disconnectionCallback);
    ble.gattServer().onDataWritten(onDataWrittenCallback);


    bool initialValueForLEDCharacteristic = true;
    ledServicePtr = new LEDService(ble, initialValueForLEDCharacteristic);


    /* setup advertising */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
    ble.gap().startAdvertising();


    while (true) {
        ble.waitForEvent();
    }
}


int main(void)
{
    BLE &ble = BLE::Instance();
    
    ble.init(bleInitComplete);
}

3. Test BLE

Impletion of BLE in mbed is in two steps. First, abstract of hardware via SPI. Second, porting the STM32L476 SPI data to BLE API of mbed. There is no hardware I/O defination for SPI pins in the program, since the mbeb has done all the job with ST team and the process is transparant to users. In other words, developer can not make any modifiation or redefine the pins. For developers, only BLE API matters.

With BLE instance and init, the BLE is started and function of onDataWrittenCallback() will lead the way of flashing the LED according to the Data received. No more experience with bluetooth protocols.

 

    BLE &ble = BLE::Instance();    
    ble.init(bleInitComplete);

4. Summary

I have tested and can flash the LED from any mobile application with BLE like lightblue.

1. Although not going to use MEMS sensor boards, I am going to use nucleo extension board  X-NUCLEO-IKS01A2X-NUCLEO-IKS01A2 to code with mother board of nucleo-STM32L476

The  X-NUCLEO-IKS01A2X-NUCLEO-IKS01A2 is a motion MEMS and environmental sensor expansion board for the STM32 Nucleo It is equipped with Arduino UNO R3 connector layout and is designed around the LSM6DSL 3D accelerometer and 3D gyroscope the LSM303AGR 3D accelerometer and 3D magnetometer the  HTS221HTS221 humidity and temperature sensor and the LPS22HB pressure sensor The  X-NUCLEO-IKS01A2X-NUCLEO-IKS01A2 interfaces with the STM32 microcontroller via the I²C pin I the sheme there are two I2C ports I2C1 and I2C2 While the I2C1 and I2C2 share same I/O pins so the I2C1 and I2C2 are same

According to I2C specification, I2C works in master-slave mode sharing same bus. Each slave bears unique address for communication. On boards parts are,

  • LSM303AGR MEMS 3D accelerometer (±2/±4/±8/±16 g) and MEMS3D magnetometer (±50 gauss)
  • LSM6DSL MEMS 3D accelerometer (±2/±4/±8/±16 g) and 3D gyroscope (±125/±245/±500/±1000/±2000 dps)
  • LPS22HB MEMS pressure sensor, 260-1260 hPa absolute digital output barometer
  • HTS221HTS221capacitive digital relative humidity and temperature
  • DIL24 socket for additional MEMS adapters and other sensors, this makes customized parts available.

Also, need to mention, there is X-NUCLEO-IKS01A1 board. This is quite different with 6D sensors instead of 3D sensor of IKS01A2.

2. First, create empty project from template, or you can directly import one demo HelloWorld project by ST teams,but not for IKS01A2, only for IKS01A1. But you can still use it, if right I2C slave address are set.

 

3.  After imported, the file structure is,

it is obvious what parts are used in this board. To use this template, scheme comparing is necessary. It would take extra effort. If not familiar with the difference, it is more safe to create new one.

4. Here is the code for reference,

 

/* Includes */
#include "mbed.h"
#include "x_nucleo_iks01a1.h"


/* Instantiate the expansion board */
static X_NUCLEO_IKS01A1 *mems_expansion_board = X_NUCLEO_IKS01A1::Instance(D14, D15);


/* Retrieve the composing elements of the expansion board */
static GyroSensor *gyroscope = mems_expansion_board->GetGyroscope();
static MotionSensor *accelerometer = mems_expansion_board->GetAccelerometer();
static MagneticSensor *magnetometer = mems_expansion_board->magnetometer;
static HumiditySensor *humidity_sensor = mems_expansion_board->ht_sensor;
static PressureSensor *pressure_sensor = mems_expansion_board->pt_sensor;
static TempSensor *temp_sensor1 = mems_expansion_board->ht_sensor;
static TempSensor *temp_sensor2 = mems_expansion_board->pt_sensor;


/* Helper function for printing floats & doubles */
static char *printDouble(char* str, double v, int decimalDigits=2)
{
  int i = 1;
  int intPart, fractPart;
  int len;
  char *ptr;


  /* prepare decimal digits multiplicator */
  for (;decimalDigits!=0; i*=10, decimalDigits--);


  /* calculate integer & fractinal parts */
  intPart = (int)v;
  fractPart = (int)((v-(double)(int)v)*i);


  /* fill in integer part */
  sprintf(str, "%i.", intPart);


  /* prepare fill in of fractional part */
  len = strlen(str);
  ptr = &str[len];


  /* fill in leading fractional zeros */
  for (i/=10;i>1; i/=10, ptr++) {
    if(fractPart >= i) break;
    *ptr = '0';
  }


  /* fill in (rest of) fractional part */
  sprintf(ptr, "%i", fractPart);


  return str;
}




/* Simple main function */
int main() {
  uint8_t id;
  float value1, value2;
  char buffer1[32], buffer2[32];
  int32_t axes[3];
  
  printf("\r\n--- Starting new run ---\r\n");


  humidity_sensor->read_id(&id);
  printf("HTS221  humidity & temperature    = 0x%X\r\n", id);
  pressure_sensor->read_id(&id);
  printf("LPS25H  pressure & temperature    = 0x%X\r\n", id);
  magnetometer->read_id(&id);
  printf("LIS3MDL magnetometer              = 0x%X\r\n", id);
  gyroscope->read_id(&id);
  printf("LSM6DS0 accelerometer & gyroscope = 0x%X\r\n", id);
  
  wait(3);
 
  while(1) {
    printf("\r\n");


    temp_sensor1->get_temperature(&value1);
    humidity_sensor->get_humidity(&value2);
    printf("HTS221: [temp] %7s°C,   [hum] %s%%\r\n", printDouble(buffer1, value1), printDouble(buffer2, value2));
    
    temp_sensor2->get_fahrenheit(&value1);
    pressure_sensor->get_pressure(&value2);
    printf("LPS25H: [temp] %7s°F, [press] %smbar\r\n", printDouble(buffer1, value1), printDouble(buffer2, value2));


    printf("---\r\n");


    magnetometer->get_m_axes(axes);
    printf("LIS3MDL [mag/mgauss]:  %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    accelerometer->get_x_axes(axes);
    printf("LSM6DS0 [acc/mg]:      %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    gyroscope->get_g_axes(axes);
    printf("LSM6DS0 [gyro/mdps]:   %7ld, %7ld, %7ld\r\n", axes[0], axes[1], axes[2]);


    wait(1.5);
  }
}

5. Click 'compile', one exactable .bin file is build and download automatically. Drag and copy it to nucleo-board disk, flashing the memory.  It will run automatically

 

.

6. Then find your com-port for nucleo,

Use tools like putty to open serial port in 9600 baud rate, you will see.

It works. This is done by remark some unresolved lines. The sensors can easily be read via mbed demo codes.

7. If try to use other IDE on native library, it would be more complicated. First, clock shall be configured, the port setting, I2C parameter setting, interrution, sensor initializing, util then can you start the sensor and read the sensor value. But in this case, all you can say that matters in the IDE is only tow lines.

  humidity_sensor->read_id(&id);
  printf("HTS221  humidity & temperature    = 0x%X\r\n", id);

Isn't it wonderful?

1. All the design shall be based on mbed development environment for easy of use and good compatibility with STM-Nucleo board. It is not the case for other brand. In fact the mbed is not fully open sources, some definition is out of the reach of developers.

Meantime, the mbed keeps evolving, it can be seen that the source link of  https://developer.mbed.org/  has been changed into https://os.mbed.com/ , when mbed OS 5 is published. That is interesting to tracing the pace of ARM in mbed project.

2. The following screenshot shows how mbed works.

This is especially good for IoT prototype. One can change the hardware platform even without any modification to codes on mbed. At the end of the blog, I will show how to port the project to Nucleo-STM-F401 board. There would be few change to the code.

3. I have make one demo project in as flyme_test,

 

As you can see that, the layout is like most of the IDE like IAR, or Eclipse. One can use it without refer to any manual. The memory usage Diagram can be of help for big design.

One can import useful library for hardware used in the project. Fortunately, I have found and all of the necessary library for my design. That would make a quick design and deployment.

1. Before introduce the hardware, it would be interesting to share one similar Design on Kickstarter.com.

1.png

Of course, this paper airplane has no wheels. But it is really fun. And it shows some similarity with my idea, one thrown and let it go. My design is a glider crawl on road and water, hopefully, it can fly in the end.

I have calculated that at least 0.5 meter wing span is needed for the glider to fly.

2. There have been many projects with Nucleo-L476 for its low power consumption and versatile function, and low cost.

en.nucleo-Lx.jpg

 

STM32-L476 features in QFP64 package and Arduino Uno V3 connectivity and ST morpho extension pin headers for full access to all STM32 I/Os.

On-board ST-LINK/V2-1 debugger/programmer with SWD connector can make the development board one u-disk, programming with drag-and-copy, reduce the need for complex IDEs for some quick project deployment. The st-link can be used for other extension board like the st-SentorTile.

The development with ARM® mbed makes the coding even good for kids. But proper hardware driver is needed, since at the bottom level of mbed-os, it is still not fully open-sources.

Other component on the boards includes,

Three LEDs:USB communication (LD1), user LED (LD2), power LED (LD3)

Two push-buttons: USER and RESET

USB re-enumeration capability. Three different interfaces supported on USB:Virtual COM port, Mass storage, Debug port

3. X-NUCLEO-IDB04A1 shall be used in my design although it is not recommended in new design, I would change to new parts if it arrives.

en.image_x-nucleo-idb04a1.jpg

The X-NUCLEO-IDB04A1 is a Bluetooth low energy evaluation board to allow expansion of the STM32 Nucleo boards It is compatible with the Arduino UNO R3 connector layout and is designed around BlueNRG a Bluetooth low energy low power network coprocessor compliant with BTLE 4.0 and the  BALF-NRG-01D3BALF-NRG-01D3 The X-NUCLEO-IDB04A1 interfaces with the STM32 MCU via SPI pin

捕获.JPG.

Now bluetooth 4.0 is old protocol, since even bluetooth 5.0 is ready to move on. But it works fine for this design and I can use it as prototype design.

1. Introduction of the design

Design a kind of Glider with wheels to roll on road and floating barrel to float on water.

The glider is driven by propellers and Wheels are passive.

So the glider can not fly in this challenge, but it is ready to be thrown by hand landed on road or lake, if not crashed.

2. Schedule

- Evaluation blog to STM32-L476 and BlueNRG board.(1 blog)

- Demo design on mbed.org..(1 blog)

- MBED program for L476+BlueNRG to link mobile via evothings..(2 blog)

- Making of structure of glider .(1 blog)

- Make of propeller and motor for the glider .(1 blog)

- Mounting the Boards one the clumsy Glider .(1 blog)

- Test and run the glider .(1~3 blog)

- Final Show of the design

3. Hard ware and software

Nucleo-L476RG and extension board of BlueNRG shall be used for remote control by mobile

Two motors shall be controlled by ST-STM32L476-nucleo to move forward or turn left or right.

Software of CubeMX for ST shall be used.

4. Challenge and Afterward

The plane can not fly on air, but can float on water and move on road. Driven by propeller.

I would design PCB board integrating L476 and BLE chip to make the glider self-pilot.

2.jpg

This picture is a toy, I would make it sizable for Nucleo board and batteries.