Color
Background color
Background image
Border Color
Font Type
Font Size
  1. Introduction
    Hydroponics is the science of growing plants without soil. Nutrients are fed directly to the plant via nutrient solution in a very carefully monitored environment. This level of control allows us to predict various parameters such as yield, time to bear fruit, etc.
    There are certain requirements that need to be met in order to be able to control this environment successfully. Sensors need to be scanned periodically, data collected, stored and available on the cloud for viewing and monitoring from a remote location.
    The MAX32630FTHR suits this requirement perfectly. An ARM processor which comfortably processes all the sensor activity, SD Card on board which is used here to set up the unit (configuration), a gyro and accelerometer to monitor and alert the user if any disturbances occur and BLE to be able to use a mobile phone or tablet to view data and also to upload the data to the cloud.


    BOM
    MAX32630FTHR
    MAX7219 - 4 nos.
    74HC164 - 5 nos.
    Light Dependent Resistors - 9 nos.
    MH-Z14 CO2 sensor - 1 no.
    pH Electrode - 3 nos.
    Water Level Sensor - 3 nos.
    DHT-11 - Humidity/Temperature Sensor - 3 nos.
    74HC4067 - ADC MUX - 2 nos.


    Schematics



    Instructions
    Discussed in previous blogs
    Video
    Just a small clip to show most of the sensors hooked up and the actuators kicking in to light up the unit or switch on the fans. Also seen in the clip are the BLE notifications coming in from the unit. Since we are using custom characteristics, the values are not recognized in any particular format and are also not named since this is a proprietary software that I am using to check the notifications..




    Source Code
    Code (Text):
    1.  
    2. #include "mbed.h"
    3. #include "max32630fthr.h"
    4. #include "shiftreg.h"
    5. #include "adc.h"
    6. #include "Dht11.h"
    7. #include "bmi160.h"
    8. #include "ble/BLE.h"
    9. #include "ble/services/BatteryService.h"
    10. #include "ble/services/EnvironmentalService.h"
    11. #include "DeviceInformationService.h"
    12. #include "FATFileSystem.h"
    13. #include "SDBlockDevice.h"
    14.  
    15.  
    16. #define MAX_RELAYS                 8
    17. #define MAX_ADC                    32
    18. #define    MAX_SUBSYSTEMS            6        // temperature, humidity, pH, CO2, Oxygen, Water Level
    19. #define    GYRO_TRIGGER            5.0        // no real need for this to be less than 20, really
    20. #define GYRO_SCAN_INTERVAL        1.0      // seconds - consider bumping this to 200ms
    21. #define    ADC_SETTLE_TIME            20    // milliseconds
    22.  
    23. #define    ALL_GREEN_OFFSET        204
    24. #define    QUAD1_OFFSET            1
    25. #define    QUAD2_OFFSET            16
    26. #define    QUAD3_OFFSET            32
    27. #define    QUAD4_OFFSET            2
    28.  
    29. //////////////////////////////////////
    30. //
    31. //ADC MUX INPUT DEFINITIONS
    32.  
    33. #define T1HUM                    0
    34. #define T2HUM                    1
    35. #define T3HUM                    2
    36.  
    37. #define T1CO2                    3
    38. #define T2CO2                    4
    39. #define T3CO2                    5
    40.  
    41. #define T1pH                    6
    42. #define T2pH                    7
    43. #define T3pH                    8
    44.  
    45. #define    T1WLEVEL                9
    46. #define    T2WLEVEL                10
    47. #define    T3WLEVEL                11
    48.  
    49. #define    T1L1                    16
    50. #define    T1L2                    17
    51. #define    T1L3                    18
    52. #define    T2L1                    19
    53. #define    T2L2                    20
    54. #define    T2L3                    21
    55. #define    T3L1                    22
    56. #define    T3L2                    23
    57. #define    T3L3                    24
    58.  
    59. //////////////////////////////////////
    60.  
    61. //RELAYS DEFINITIONS
    62. #define    RELAY_T1_LIGHT1        0
    63. #define    RELAY_T1_LIGHT2        1
    64. #define    RELAY_T1_LIGHT3        2
    65. #define    RELAY_T2_LIGHT1        3
    66. #define    RELAY_T2_LIGHT2        4
    67. #define    RELAY_T2_LIGHT3        5
    68. #define    RELAY_T3_LIGHT1        6
    69. #define    RELAY_T3_LIGHT2        7
    70. #define    RELAY_T3_LIGHT3        8
    71. #define    RELAY_T1_FAN        9
    72. #define    RELAY_T2_FAN        10
    73. #define    RELAY_T3_FAN        11
    74. #define    RELAY_T1_OXYPUMP    12
    75. #define    RELAY_T2_OXYPUMP    13
    76. #define    RELAY_T3_OXYPUMP    14
    77. #define    RELAY_T1_CO2        15
    78. #define    RELAY_T2_CO2        16
    79. #define    RELAY_T3_CO2        17
    80.  
    81.  
    82.  
    83.     ////////////////////////////////////////////////////////////////////////////
    84.     // BMI160 Initialization
    85.     I2C i2cBus(P5_7, P6_0);
    86.     BMI160_I2C imu(i2cBus, BMI160_I2C::I2C_ADRS_SDO_LO);
    87.     BMI160::AccConfig accConfig;
    88.     BMI160::GyroConfig gyroConfig;
    89.  
    90.  
    91.  
    92.  
    93. // Shift Register Initialization
    94. ShiftOut sr(P3_5,P3_2,P3_3,24); // CLK, DATA, CLR  for the 3 74LS164's handling the relays
    95. ShiftOut adcmux(P5_4,P5_5,P5_3,8); // CLK, DATA, CLR for the single 164 handling the ADC signal multiplexers
    96. ShiftOut levelmon(P3_0,P3_1,P4_0,8); // CLK, DATA, CLR for the single 164 displaying level disturbances (dual LEDs)
    97.  
    98. // DHT-11 Initialization
    99. Dht11 hum_sensor1(P5_6);
    100. Dht11 hum_sensor2(P5_6);
    101. Dht11 hum_sensor3(P5_6);
    102.  
    103. // SPI Initialization - for MAX7219
    104. SPI spi(P5_1, P5_2, P5_0);          //  MOSI, MISO, SCLK - MISO not used here, though
    105. DigitalOut cs(P3_4);                // Chip select (LOAD)
    106.  
    107. // On board LED
    108. DigitalOut rLED(LED1, LED_OFF);
    109. DigitalOut gLED(LED2, LED_OFF);
    110. DigitalOut bLED(LED3, LED_OFF);
    111.  
    112. //Level Monitor Reset Pin
    113.  
    114. BatteryService *batteryServicePtr;
    115. DeviceInformationService *deviceInformationServicePtr;
    116. EnvironmentalService *environServicePtr;
    117.  
    118. char config_buffer[128];
    119.  
    120. const char     DEVICE_NAME[]        = "HYGROMAX630";
    121. const uint16_t uuid16_list[]        = {    GattService::UUID_BATTERY_SERVICE,
    122.                                         GattService::UUID_DEVICE_INFORMATION_SERVICE,
    123.                                         GattService::UUID_ENVIRONMENTAL_SERVICE};
    124.  
    125. int    mon_temperature        =     77;    // farenheit
    126. int mon_humidity        =    85;    // percentage
    127. int mon_pH                =    75;    // pH*10
    128. int mon_fans            =    7;    // powers of 2 format
    129. int mon_lights            =    32; // powers of 2 format
    130. int mon_waterlevel        =    80;    // percentage
    131. int    mon_oxygen            =    1;    // 1 or 0
    132. int    mon_co2                =    99;    // ppm
    133.  
    134. int    batteryPercentage    = 100;
    135.  
    136. Ticker sec_tick;
    137. Ticker fivesecs_tick;
    138. Ticker fifteensecs_tick;
    139. Ticker eighteensecs_tick;
    140. Ticker levelmon_tick;
    141.  
    142. int mc_seconds = 0;
    143. int mc_minutes = 39;
    144. int mc_hours = 10;
    145. int mc_days = 17;
    146. int start_hours = 0;
    147. int start_minutes = 0;
    148.  
    149.  
    150. int lightintensitytrigger = 500;   // probably needs further calibration
    151.                                   // also, consider moving this to the SD card
    152.                                  // along with the others
    153.  
    154. int lightdutycycle = 0;
    155. int co2trigger = 0;
    156. int pHtrigger = 0;
    157. int lp_lights = 0;
    158. int lp_oxygen = 0;
    159. int lp_co2 = 0;
    160.  
    161.  
    162. int    subsys_count = 0;
    163.  
    164. int adc_int;
    165.  
    166. float imuTemperature;
    167. BMI160::SensorData accData;
    168. BMI160::SensorData gyroData;
    169. BMI160::SensorTime sensorTime;
    170. int disturbed = 0;
    171. int QUAD1 = 0;
    172. int QUAD2 = 0;
    173. int QUAD3 = 0;
    174. int QUAD4 = 0;
    175.  
    176.  
    177. int RELAYS[MAX_RELAYS];
    178. int srcount = 0;
    179. int adc_count = 3;  //0,1,2 - humidity
    180.  
    181. int TWOPOWERS[] = {1,
    182.         2,
    183.         4,
    184.         8,
    185.         16,
    186.         32,
    187.         64,
    188.         128,
    189.         256,
    190.         512,
    191.         1024,
    192.         2048,
    193.         4096,
    194.         8192,
    195.         16384,
    196.         32768,
    197.         65536,
    198.         131072,
    199.         262144,
    200.         524288,
    201.         1048576,
    202.         2097152,
    203.         4194304,
    204.         8388608,
    205.         16777216};
    206.  
    207. unsigned char ADC_SELECT[32] = {32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
    208.                                 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
    209.  
    210.  
    211. unsigned char BUFFER_MSB[4][8];
    212. unsigned char BUFFER_LSB[4][8];
    213.  
    214.  
    215. void periodicCallback(void)
    216. {
    217.  
    218.  
    219. }
    220.  
    221. /* Restart Advertising on disconnection*/
    222. void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
    223. {
    224.     BLE::Instance().gap().startAdvertising();
    225. }
    226.  
    227. /* Connection */
    228. void connectionCallback(const Gap::ConnectionCallbackParams_t *params)
    229. {
    230. }
    231.  
    232.  
    233. /**
    234. * This function is called when the ble initialization process has failed
    235. */
    236. void onBleInitError(BLE &ble, ble_error_t error)
    237. {
    238.     /* Avoid compiler warnings */
    239.     (void) ble;
    240.     (void) error;
    241.     /* Initialization error handling should go here */
    242. }
    243.  
    244. /**
    245. * Callback triggered when the ble initialization process has finished
    246. */
    247. void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
    248. {
    249.     BLE&        ble   = params->ble;
    250.     ble_error_t error = params->error;
    251.  
    252.     if (error != BLE_ERROR_NONE) {
    253.         /* In case of error, forward the error handling to onBleInitError */
    254.         onBleInitError(ble, error);
    255.         return;
    256.     }
    257.  
    258.     /* Ensure that it is the default instance of BLE */
    259.     if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    260.         return;
    261.     }
    262.  
    263.     ble.gap().onDisconnection(disconnectionCallback);
    264.     ble.gap().onConnection(connectionCallback);
    265.  
    266.     /* Setup primary service. */
    267.     deviceInformationServicePtr = new DeviceInformationService(ble, "Maxim", "FTHR", "00001", "0.1", "0.0", "0.0");
    268.     batteryServicePtr = new BatteryService(ble, batteryPercentage);
    269.     environServicePtr = new EnvironmentalService(ble);
    270.  
    271.     /* Setup advertising */
    272.     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    273.     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    274.     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    275.     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    276.     ble.gap().setAdvertisingInterval(250); /* 250ms */
    277.     ble.gap().startAdvertising();
    278. }
    279.  
    280. void ClearLoadPin()
    281. {
    282.     cs = 0;                         // Set CS Low
    283. }
    284.  
    285. void SetLoadPin()
    286. {
    287.     cs = 1;                         // Set CS High
    288. }
    289.  
    290. /// Send two bytes to SPI bus
    291. void SPI_Write2(unsigned char MSB, unsigned char LSB)
    292. {
    293.     spi.write(MSB);                 // Send two bytes
    294.     spi.write(LSB);
    295.  
    296. }
    297.  
    298. void ReadADC(int select)
    299. {
    300.     float adc_f = 0.0;
    301.     float adc_norm = 0.0;
    302.  
    303.     uint16_t adc_value;
    304.     unsigned int overflow;
    305.     ADC_StartConvert(ADC_CH_1_DIV_5, 0, 1); // AIN1 div 5
    306.     overflow = (ADC_GetData(&adc_value) == E_OVERFLOW ? 1 : 0);
    307.     printf("AIN1/5: 0x%04x%s  %d    %d\r\n", adc_value, overflow ? "*" : " ", ADC_SELECT[select], adc_value);
    308.     adc_f = adc_value;
    309.     adc_norm = (adc_f/1024.0)*100.0;
    310.     adc_int = adc_norm;
    311.     if(adc_int > 99)
    312.         adc_int = 99;
    313. }
    314. /*
    315. void TestADCMUX()
    316. {
    317.     ClearAllRelays();
    318.     adcmux.setclr();
    319.  
    320.     if(adc_count==MAX_RELAYS)
    321.     {
    322.            adc_count = 3;  //0,1,2 - humidity
    323.            wait(1.0);
    324.     }
    325.  
    326.     adcmux.write(ADC_SELECT[adc_count]);
    327.     wait_ms(1);
    328.     ReadADC(adc_count);
    329.  
    330.     SetRelay(adc_count);
    331.     int relay_data = GetRelayData();
    332.     sr.write(relay_data);
    333.     wait(1);
    334. }
    335. */
    336.  
    337. /// Send eight control bytes to SPI bus to 4 7219's
    338. void SPI_Write8(unsigned char MSB, unsigned char LSB)
    339. {
    340.     ClearLoadPin();
    341.     spi.write(MSB);                 // Send two bytes to 4
    342.     spi.write(LSB);
    343.     spi.write(MSB);                 // Send two bytes to 3
    344.     spi.write(LSB);
    345.     spi.write(MSB);                 // Send two bytes to 2
    346.     spi.write(LSB);
    347.     spi.write(MSB);                 // Send two bytes to 1
    348.     spi.write(LSB);
    349.     SetLoadPin();
    350. }
    351.  
    352. /// Send eight bytes to SPI bus to 4 7219's
    353. void SPI_Write8BUF()
    354. {
    355.     for(int digit=0;digit<8;digit++)
    356.     {
    357.         ClearLoadPin();
    358.  
    359.         spi.write(BUFFER_MSB[3][digit]);                 // Send two bytes to 3
    360.         spi.write(BUFFER_LSB[3][digit]);
    361.         spi.write(BUFFER_MSB[2][digit]);                 // Send two bytes to 2
    362.         spi.write(BUFFER_LSB[2][digit]);
    363.         spi.write(BUFFER_MSB[1][digit]);                 // Send two bytes to 1
    364.         spi.write(BUFFER_LSB[1][digit]);
    365.         spi.write(BUFFER_MSB[0][digit]);                 // Send two bytes to 4
    366.         spi.write(BUFFER_LSB[0][digit]);
    367.  
    368.         SetLoadPin();
    369.     }
    370. }
    371.      
    372. void SPI_WriteToBuf(int bufslot, int digit, unsigned char MSB, unsigned char LSB)
    373. {
    374.        BUFFER_MSB[bufslot][digit] = MSB;              
    375.        BUFFER_LSB[bufslot][digit] = LSB;
    376.      
    377. }
    378.  
    379. void Update_MAX7219()
    380. {
    381.                  
    382.           SPI_Write8BUF();
    383. }
    384.  
    385.  
    386.  
    387. /// MAX7219 initialisation
    388. void Init_MAX7219(void)
    389. {
    390.     SPI_Write8(0x09, 0xFF);         // Decoding on
    391.     SPI_Write8(0x0A, 0x08);         // Brightness to intermediate
    392.     SPI_Write8(0x0B, 0x07);         // Scan limit = 7
    393.     SPI_Write8(0x0C, 0x01);         // Normal operation mode
    394.     SPI_Write8(0x0F, 0x0F);         // Enable display test
    395.     wait_ms(100);                   // 500 ms delay
    396.     /*
    397.     SPI_Write8(0x01, 0x00);         // Clear row 0.
    398.     SPI_Write8(0x02, 0x00);         // Clear row 1.
    399.     SPI_Write8(0x03, 0x00);         // Clear row 2.
    400.     SPI_Write8(0x04, 0x00);         // Clear row 3.
    401.     SPI_Write8(0x05, 0x00);         // Clear row 4.
    402.     SPI_Write8(0x06, 0x00);         // Clear row 5.
    403.     SPI_Write8(0x07, 0x00);         // Clear row 6.
    404.     SPI_Write8(0x08, 0x00);         // Clear row 7.
    405.     */
    406.     SPI_Write8(0x0F, 0x00);         // Disable display test
    407.     wait_ms(100);
    408. }
    409.  
    410. int GetRelayData()
    411. {
    412.     int rd = 0;
    413.     for(int i=0;i<MAX_RELAYS;i++)
    414.         if(RELAYS[i])
    415.             rd += TWOPOWERS[i];
    416.     return(rd);
    417. }
    418.  
    419.  
    420. void ScanLight()
    421. {
    422.     adcmux.setclr();
    423.     adcmux.write(ADC_SELECT[T1L1]);
    424.     wait_ms(1);
    425.     printf("Light T1L1 - ");
    426.     ReadADC(T1L1);
    427.     SPI_WriteToBuf(1,0,1,13);
    428.     SPI_WriteToBuf(1,1,2,1);
    429.     SPI_WriteToBuf(1,2,3,(adc_int/10));
    430.     SPI_WriteToBuf(1,3,4,(adc_int%10));
    431.  
    432.     adcmux.setclr();
    433.     adcmux.write(ADC_SELECT[T1L2]);
    434.     wait_ms(1);
    435.     printf("Light T1L2 -");
    436.     ReadADC(T1L2);
    437.     SPI_WriteToBuf(1,4,5,(adc_int/10));
    438.     SPI_WriteToBuf(1,5,6,(adc_int%10));
    439.  
    440.     adcmux.setclr();
    441.     adcmux.write(ADC_SELECT[T1L3]);
    442.     wait_ms(1);
    443.     printf("Light T1L3 - ");
    444.     ReadADC(T1L3);
    445.     SPI_WriteToBuf(1,6,7,(adc_int/10));
    446.     SPI_WriteToBuf(1,7,8,(adc_int%10));
    447.  
    448.     adcmux.setclr();
    449.     adcmux.write(ADC_SELECT[T2L1]);
    450.     wait_ms(1);
    451.     printf("Light T2L1 - ");
    452.     ReadADC(T2L1);
    453.     SPI_WriteToBuf(2,0,1,13);
    454.     SPI_WriteToBuf(2,1,2,1);
    455.     SPI_WriteToBuf(2,2,3,(adc_int/10));
    456.     SPI_WriteToBuf(2,3,4,(adc_int%10));
    457.  
    458.     adcmux.setclr();
    459.     adcmux.write(ADC_SELECT[T2L2]);
    460.     wait_ms(1);
    461.     printf("Light T2L2 - ");
    462.     ReadADC(T2L2);
    463.     SPI_WriteToBuf(2,4,5,(adc_int/10));
    464.     SPI_WriteToBuf(2,5,6,(adc_int%10));
    465.  
    466.     adcmux.setclr();
    467.     adcmux.write(ADC_SELECT[T2L3]);
    468.     wait_ms(1);
    469.     printf("Light T2L3 - ");
    470.     ReadADC(T2L3);
    471.     SPI_WriteToBuf(2,6,7,(adc_int/10));
    472.     SPI_WriteToBuf(2,7,8,(adc_int%10));
    473.  
    474.     adcmux.setclr();
    475.     adcmux.write(ADC_SELECT[T3L1]);
    476.     wait_ms(1);
    477.     printf("Light T3L1 - ");
    478.     ReadADC(T3L1);
    479.     SPI_WriteToBuf(3,0,1,13);
    480.     SPI_WriteToBuf(3,1,2,1);
    481.     SPI_WriteToBuf(3,2,3,(adc_int/10));
    482.     SPI_WriteToBuf(3,3,4,(adc_int%10));
    483.  
    484.     adcmux.setclr();
    485.     adcmux.write(ADC_SELECT[T3L2]);
    486.     wait_ms(1);
    487.     printf("Light T3L2 -  ");
    488.     ReadADC(T3L2);
    489.     SPI_WriteToBuf(3,4,5,(adc_int/10));
    490.     SPI_WriteToBuf(3,5,6,(adc_int%10));
    491.  
    492.     adcmux.setclr();
    493.     adcmux.write(ADC_SELECT[T3L3]);
    494.     wait_ms(1);
    495.     printf("Light T3L3 - ");
    496.     ReadADC(T3L3);
    497.     SPI_WriteToBuf(3,6,7,(adc_int/10));
    498.     SPI_WriteToBuf(3,7,8,(adc_int%10));
    499.  
    500. }
    501.  
    502. void ScanHumidity()
    503. {
    504.     adcmux.setclr();
    505.     adcmux.write(ADC_SELECT[T1HUM]);
    506.     wait_ms(ADC_SETTLE_TIME);
    507.     hum_sensor1.read();
    508.     wait_ms(ADC_SETTLE_TIME);
    509.     float ftemp1 = hum_sensor1.getFahrenheit();
    510.     int ihum = hum_sensor1.getHumidity();
    511.     printf("T: %f, H: %d\r\n",ftemp1 , ihum);
    512.     SPI_WriteToBuf(1,0,1,12);
    513.     SPI_WriteToBuf(1,1,2,10);
    514.     SPI_WriteToBuf(1,2,3,(ihum/10));
    515.     SPI_WriteToBuf(1,3,4,(ihum%10));
    516.     int itemp = ftemp1;
    517.     SPI_WriteToBuf(1,5,6,(itemp/10));
    518.     SPI_WriteToBuf(1,6,7,(itemp%10));
    519.     SPI_WriteToBuf(1,7,8,14);
    520.  
    521.     adcmux.setclr();
    522.     adcmux.write(ADC_SELECT[T2HUM]);
    523.     wait_ms(ADC_SETTLE_TIME);
    524.     hum_sensor2.read();
    525.     wait_ms(ADC_SETTLE_TIME);
    526.     float ftemp2 = hum_sensor2.getFahrenheit();
    527.     ihum = hum_sensor2.getHumidity();
    528.     printf("T: %f, H: %d\r\n",ftemp2 , ihum);
    529.     SPI_WriteToBuf(2,0,1,12);
    530.     SPI_WriteToBuf(2,1,2,10);
    531.     SPI_WriteToBuf(2,2,3,(ihum/10));
    532.     SPI_WriteToBuf(2,3,4,(ihum%10));
    533.     itemp = ftemp2;
    534.     SPI_WriteToBuf(2,5,6,(itemp/10));
    535.     SPI_WriteToBuf(2,6,7,(itemp%10));
    536.     SPI_WriteToBuf(2,7,8,14);
    537.  
    538.     adcmux.setclr();
    539.     adcmux.write(ADC_SELECT[T3HUM]);
    540.     wait_ms(ADC_SETTLE_TIME);
    541.     hum_sensor3.read();
    542.     wait_ms(ADC_SETTLE_TIME);
    543.     float ftemp3 = hum_sensor3.getFahrenheit();
    544.     ihum = hum_sensor3.getHumidity();
    545.     printf("T: %f, H: %d\r\n",ftemp3 , ihum);
    546.     SPI_WriteToBuf(3,0,1,12);
    547.     SPI_WriteToBuf(3,1,2,10);
    548.     SPI_WriteToBuf(3,2,3,(ihum/10));
    549.     SPI_WriteToBuf(3,3,4,(ihum%10));
    550.     itemp = ftemp3;
    551.     SPI_WriteToBuf(3,5,6,(itemp/10));
    552.     SPI_WriteToBuf(3,6,7,(itemp%10));
    553.     SPI_WriteToBuf(3,7,8,14);
    554.     /*
    555.     if((ftemp1>80.0)||(ftemp2>80.0)||(ftemp3>80))
    556.     {
    557.         RELAYS[21] = 1;
    558.         RELAYS[22] = 1;
    559.         RELAYS[23] = 1;
    560.     }
    561.     else
    562.     {
    563.         RELAYS[21] = 0;
    564.         RELAYS[22] = 0;
    565.         RELAYS[23] = 0;    
    566.     }
    567.     int relay_data = GetRelayData();
    568.     sr.write(relay_data);
    569.     */
    570. }
    571.  
    572. void ScanCO2()
    573. {
    574.     adcmux.setclr();
    575.     adcmux.write(ADC_SELECT[T1CO2]);
    576.     wait_ms(1);
    577.     printf("CO2  T1CO2 - ");
    578.     ReadADC(T1CO2);
    579.     SPI_WriteToBuf(1,0,1,14);
    580.     SPI_WriteToBuf(1,1,2,14);
    581.     SPI_WriteToBuf(1,2,3,10);
    582.     SPI_WriteToBuf(1,3,4,(adc_int/10));
    583.     SPI_WriteToBuf(1,4,5,(adc_int%10));
    584.  
    585.     SPI_WriteToBuf(2,0,1,14);
    586.     SPI_WriteToBuf(2,1,2,14);
    587.     SPI_WriteToBuf(2,2,3,10);
    588.     SPI_WriteToBuf(2,3,4,(adc_int/10));
    589.     SPI_WriteToBuf(2,4,5,(adc_int%10));
    590.  
    591.     SPI_WriteToBuf(3,0,1,14);
    592.     SPI_WriteToBuf(3,1,2,14);
    593.     SPI_WriteToBuf(3,2,3,10);
    594.     SPI_WriteToBuf(3,3,4,(adc_int/10));
    595.     SPI_WriteToBuf(3,4,5,(adc_int%10));
    596. }
    597.  
    598. void ScanOxygen()
    599. {
    600.     if(RELAYS[RELAY_T1_OXYPUMP] == 1)
    601.         printf("Oxygen Flow is ON \r\n");
    602.     else
    603.         printf("Oxygen Flow is OFF \r\n");
    604. }
    605.  
    606.  
    607. void UpdateClock()
    608. {
    609.     int sec_tens = mc_seconds / 10;
    610.     int sec_units = mc_seconds % 10;
    611.     int min_tens = mc_minutes / 10;
    612.     int min_units = mc_minutes % 10;
    613.     int hour_tens = mc_hours / 10;
    614.     int hour_units = mc_hours % 10;
    615.     int day_tens = mc_days / 10;
    616.     int day_units = mc_days % 10;
    617.  
    618.     SPI_WriteToBuf(0,0,1,hour_tens);
    619.     SPI_WriteToBuf(0,1,2,hour_units);
    620.     SPI_WriteToBuf(0,2,3,min_tens);
    621.     SPI_WriteToBuf(0,3,4,min_units);
    622.     SPI_WriteToBuf(0,4,5,sec_tens);
    623.     SPI_WriteToBuf(0,5,6,sec_units);
    624.     SPI_WriteToBuf(0,6,7,day_tens);
    625.     SPI_WriteToBuf(0,7,8,day_units);
    626.  
    627.     // Get our ADC mux to fire every 2 seconds without setting up an extra ticker for this
    628.     if((mc_seconds % 2)==0)
    629.     {
    630.         if(subsys_count==MAX_SUBSYSTEMS)
    631.             subsys_count = 0;
    632.         int selected_disp = subsys_count % MAX_SUBSYSTEMS;
    633.         switch (selected_disp)
    634.         {
    635.         case 0:
    636.             //Light
    637.             ScanLight();
    638.             break;
    639.         case 1:
    640.             // Humidity Temperature
    641.             ScanHumidity();
    642.             break;
    643.         case 2:
    644.             // Oxygen
    645.             //ScanOxygen();
    646.             break;
    647.         case 3:
    648.             //CO2
    649.             ScanCO2();
    650.             break;
    651.         case 4:
    652.             //pH
    653.             //ScanpH();
    654.             break;
    655.         case 5:
    656.             //Water Level
    657.             //ScanWaterLevel();
    658.             break;
    659.         }
    660.         subsys_count++;
    661.     } // mc_seconds % 2
    662.  
    663.     Update_MAX7219();
    664.  
    665.  
    666.     Update_MAX7219();
    667. }
    668.  
    669. void MonitorLevel()
    670. {
    671.     imu.getGyroAccXYZandSensorTime(accData, gyroData, sensorTime, accConfig.range, gyroConfig.range);
    672.     imu.getTemperature(&imuTemperature);
    673.  
    674.     if(!disturbed)
    675.     {
    676.      
    677.        //    QUADRANTS
    678.        //
    679.        //     2     1
    680.        //  
    681.        //     3     4
    682.      
    683.        int xval = gyroData.xAxis.scaled;
    684.        int yval = gyroData.yAxis.scaled;
    685.        //int zval = gyroData.zAxis.scaled; // we'll come back for this some other day
    686.      
    687.        if((QUAD1!=0)||(QUAD2!=0)||(QUAD3!=0)||(QUAD4!=0))
    688.             disturbed = 1;
    689.      
    690.        if(xval > GYRO_TRIGGER)  // 3 or 4 lowered
    691.           if(yval > GYRO_TRIGGER) // 4
    692.               QUAD4 = 1;
    693.           else if(yval < -(GYRO_TRIGGER))  //3
    694.               QUAD3 = 1;
    695.           else // 3 and 4
    696.               QUAD3 = QUAD4 = 1;
    697.        else if(xval < -(GYRO_TRIGGER))  // 1 or 2 lowered
    698.           if(yval > GYRO_TRIGGER) // 1
    699.               QUAD1 = 1;
    700.           else if(yval < -(GYRO_TRIGGER))  //2
    701.               QUAD2 = 1;
    702.           else
    703.               QUAD1 = QUAD2 = 1;
    704.        else if(yval > GYRO_TRIGGER) // 1 and 4
    705.           QUAD1 = QUAD4 = 1;
    706.        else if(yval < -(GYRO_TRIGGER)) // 2 and 3
    707.           QUAD2 = QUAD3 = 1;
    708.        // else // basically, all is well - no disturbance
    709.        //    QUAD1 = QUAD2 = QUAD3 = QUAD4 = 0;
    710.     }  // if(!disturbed)
    711.  
    712.     levelmon.setclr();
    713.     int level_pattern = ALL_GREEN_OFFSET;
    714.     if(QUAD1==1)
    715.         level_pattern += QUAD1_OFFSET;
    716.     if(QUAD2==1)
    717.         level_pattern += QUAD2_OFFSET;
    718.     if(QUAD3==1)
    719.         level_pattern += QUAD3_OFFSET;
    720.     if(QUAD4==1)
    721.         level_pattern += QUAD4_OFFSET;
    722.  
    723.     levelmon.write(level_pattern);
    724.  
    725.  
    726. }
    727.  
    728. void SetRelay(int relno)
    729. {
    730.     RELAYS[relno] = 1;
    731. }
    732.  
    733. void ClearRelay(int relno)
    734. {
    735.     RELAYS[relno] = 0;
    736. }
    737.  
    738. void ClearAllRelays(){
    739.     for(int i=0;i<MAX_RELAYS;i++)
    740.         RELAYS[i] = 0;
    741.     sr.setclr();
    742. }
    743.  
    744.  
    745.  
    746. void TestAllRelays()
    747. {
    748.     if(srcount==MAX_RELAYS)
    749.     {
    750.        srcount = 0;
    751.        ClearAllRelays();
    752.     }
    753.     SetRelay(srcount);
    754.     int relay_data = GetRelayData();
    755.     sr.write(relay_data);
    756.     printf("RELAYS SWITCHED ON - %d\r\n", relay_data);
    757.     wait_ms(10);
    758.     srcount++;
    759. }
    760.  
    761.  
    762. void EverySecond()
    763. {
    764.  
    765.     gLED = !gLED;
    766.     bLED = !bLED;
    767.     rLED = !rLED; /* Blink LED while we're waiting for BLE events */
    768.  
    769.  
    770.     batteryServicePtr->updateBatteryLevel(batteryPercentage);
    771.  
    772.     environServicePtr->updateTemperature(mon_temperature);
    773.     environServicePtr->updateHumidity(mon_humidity);
    774.  
    775.     environServicePtr->updatepH(mon_pH);
    776.     environServicePtr->updateLights(mon_lights);
    777.     environServicePtr->updateWaterLevel(mon_waterlevel);
    778.     environServicePtr->updateOxygen(mon_oxygen);
    779.     environServicePtr->updateFans(mon_fans);
    780.     environServicePtr->updateCO2(mon_co2);
    781.  
    782.     /*
    783.  
    784.     printf("\033[H");  //home
    785.     printf("\033[0J");  //erase from cursor to end of screen
    786.  
    787.     printf("ACC xAxis = %s%4.3f\r\n", "\033[K", accData.xAxis.scaled);
    788.     printf("ACC yAxis = %s%4.3f\r\n", "\033[K", accData.yAxis.scaled);
    789.     printf("ACC zAxis = %s%4.3f\r\n\n", "\033[K", accData.zAxis.scaled);
    790.  
    791.     printf("GYRO xAxis = %s%5.1f\r\n", "\033[K", gyroData.xAxis.scaled);
    792.     printf("GYRO yAxis = %s%5.1f\r\n", "\033[K", gyroData.yAxis.scaled);
    793.     printf("GYRO zAxis = %s%5.1f\r\n\n", "\033[K", gyroData.zAxis.scaled);
    794.  
    795.     printf("Sensor  Time = %s%f\r\n", "\033[K", sensorTime.seconds);
    796.     printf("Sensor  Temperature = %s%5.3f\r\n", "\033[K", imuTemperature);
    797.  
    798.     printf("%d        %d\r\n\n",QUAD2,QUAD1);
    799.     printf("%d        %d\r\n\n",QUAD3,QUAD4);
    800.  
    801.     */
    802.     if(mc_seconds==59){
    803.         mc_seconds=0;
    804.         if(mc_minutes==59)
    805.         {
    806.             mc_minutes=0;
    807.             if(mc_hours==23)
    808.             {
    809.                 mc_hours=0;
    810.                 mc_days++;
    811.             }
    812.             else
    813.                 mc_hours++;
    814.         }
    815.         else
    816.             mc_minutes++;
    817.     }
    818.     else
    819.         mc_seconds++;
    820.  
    821.     UpdateClock();
    822.  
    823.  
    824. }
    825.  
    826.  
    827. void LevelResetFunc()
    828. {
    829.     disturbed = 0;
    830.     QUAD1 = QUAD2 = QUAD3 = QUAD4 = 0;
    831.  
    832.  
    833. }
    834.  
    835.  
    836.  
    837. void LoadConfigParam(int entry)
    838. {
    839.     int mc_int;
    840.     mc_int = atoi(config_buffer);
    841.     switch (entry)
    842.         {
    843.         case 0:
    844.             //Current Time
    845.             printf("Start with %d\r\n", mc_int);
    846.             mc_days = mc_int % 100;
    847.             mc_int = (mc_int - mc_days)/100;
    848.             printf("Now at %d\r\n",mc_int);
    849.             mc_seconds = mc_int % 100;
    850.             mc_int = (mc_int - mc_seconds)/100;
    851.             printf("Now at %d\r\n",mc_int);
    852.             mc_minutes = mc_int % 100;
    853.             mc_int = (mc_int - mc_minutes)/100;
    854.             printf("Now at %d\r\n",mc_int);
    855.             mc_hours = mc_int % 100;
    856.          
    857.             printf("System Clock Reset to %d hours, %d minutes, %d seconds (%d Days)\r\n", mc_hours, mc_minutes, mc_seconds, mc_days);
    858.             break;
    859.         case 1:
    860.             // Cycle Start Time
    861.             start_minutes = mc_int % 100;
    862.             start_hours = (mc_int - start_minutes)/100;
    863.             printf("Cycle Starts at %d hours and %d minutes\r\n", start_hours, start_minutes);
    864.             break;
    865.         case 2:
    866.             //Light Duty Cycle
    867.             lightdutycycle = mc_int;
    868.             printf("Light Duty Cycle set at %d hours\r\n", mc_int);
    869.             break;
    870.         case 3:
    871.             // CO2 PPM Trigger
    872.             co2trigger = mc_int;
    873.             printf("CO2 Trigger set at %d (ppm)\r\n", co2trigger);
    874.             break;
    875.         case 4:
    876.             // pH Trigger
    877.             pHtrigger = mc_int;
    878.             printf("pH Trigger set at %d (Scale x10)\r\n",pHtrigger);
    879.             break;
    880.         case 5:
    881.             // Lights - Low Power
    882.             lp_lights = mc_int;
    883.             printf("Lights on Low Power Mode - %d\r\n",lp_lights);
    884.         case 6:
    885.             // Oxygen - Low Power
    886.             lp_oxygen = mc_int;
    887.             printf("Oxygen on Low Power Mode - %d\r\n",lp_oxygen);
    888.             break;
    889.         case 7:
    890.             // CO2 - Low Power
    891.             lp_co2 = mc_int;
    892.             printf("CO2 on Low Power Mode - %d\r\n",lp_co2);
    893.             break;
    894.         }
    895. }
    896.  
    897. void ReadConfig()
    898. {
    899.     SDBlockDevice sd(P0_5,P0_6,P0_4,P0_7); // mosi, miso, sclk, cs
    900.     FATFileSystem fs("fs");
    901.  
    902.     printf("Mounting the filesystem on \"/fs\". ");
    903.     fs.mount(&sd);
    904.  
    905.     FILE* fd = fopen("/fs/config.txt", "r");
    906.  
    907.  
    908.     int entry = 0;
    909.     while(fgets(config_buffer, 128, fd)) {
    910.         LoadConfigParam(entry);
    911.         entry++;
    912.     }
    913.  
    914.     /*
    915.     char ch = fgetc(fd);
    916.     while(!feof(fd)){
    917.        printf("%c",ch);
    918.        ch = fgetc(fd);
    919.     }
    920.  
    921.  
    922.     fclose(fd);
    923. }
    924.  
    925.  
    926. int main()
    927. {
    928.     uint32_t i = 0;
    929.     LowPowerTicker ticker;
    930.     BLE &ble = BLE::Instance();
    931.     ble.init(bleInitComplete);
    932.  
    933.     /* SpinWait for initialization to complete. This is necessary because the
    934.      * BLE object is used in the main loop below. */
    935.     while (ble.hasInitialized() == false) { /* spin loop */ }
    936.     sec_tick.attach(&EverySecond,1.0);
    937.     //ticker.attach(periodicCallback, 1.5);
    938.  
    939.     // MAX32630FTHR Initialization
    940.     MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3);
    941.  
    942.     // Initialize ADC
    943.     ADC_Init();
    944.  
    945.     // BMI160 Initialization
    946.     i2cBus.frequency(400000);
    947.  
    948.     cs = 1;                         // CS initially High
    949.     spi.format(8,0);                // 8-bit format, mode 0,0
    950.     spi.frequency(1000000);         // SCLK = 1 MHz
    951.  
    952.     Init_MAX7219();                 // Initialize the LED controller
    953.    
    954.     uint32_t failures = 0;
    955.      
    956.     if(imu.setSensorPowerMode(BMI160::GYRO, BMI160::NORMAL) != BMI160::RTN_NO_ERROR)
    957.     {
    958.         printf("Failed to set gyroscope power mode\n");
    959.         failures++;
    960.     }
    961.     wait_ms(100);
    962.  
    963.     if(imu.setSensorPowerMode(BMI160::ACC, BMI160::NORMAL) != BMI160::RTN_NO_ERROR)
    964.     {
    965.         printf("Failed to set accelerometer power mode\n");
    966.         failures++;
    967.     }
    968.     wait_ms(100);
    969.  
    970.  
    971.     //example of using getSensorConfig
    972.     if(imu.getSensorConfig(accConfig) == BMI160::RTN_NO_ERROR)
    973.     {
    974.         printf("ACC Range = %d\r\n", accConfig.range);
    975.         printf("ACC UnderSampling = %d\r\n", accConfig.us);
    976.         printf("ACC BandWidthParam = %d\r\n", accConfig.bwp);
    977.         printf("ACC OutputDataRate = %d\r\n\n", accConfig.odr);
    978.     }
    979.     else
    980.     {
    981.         printf("Failed to get accelerometer configuration\r\n");
    982.         failures++;
    983.     }
    984.  
    985.     //example of setting user defined configuration
    986.     accConfig.range = BMI160::SENS_4G;
    987.     accConfig.us = BMI160::ACC_US_OFF;
    988.     accConfig.bwp = BMI160::ACC_BWP_2;
    989.     accConfig.odr = BMI160::ACC_ODR_8;
    990.     if(imu.setSensorConfig(accConfig) == BMI160::RTN_NO_ERROR)
    991.     {
    992.         printf("ACC Range = %d\r\n", accConfig.range);
    993.         printf("ACC UnderSampling = %d\r\n", accConfig.us);
    994.         printf("ACC BandWidthParam = %d\r\n", accConfig.bwp);
    995.         printf("ACC OutputDataRate = %d\r\n\n", accConfig.odr);
    996.     }
    997.     else
    998.     {
    999.         printf("Failed to set accelerometer configuration\r\n");
    1000.         failures++;
    1001.     }
    1002.  
    1003.  
    1004.     if(imu.getSensorConfig(gyroConfig) == BMI160::RTN_NO_ERROR)
    1005.     {
    1006.         printf("GYRO Range = %d\r\n", gyroConfig.range);
    1007.         printf("GYRO BandWidthParam = %d\r\n", gyroConfig.bwp);
    1008.         printf("GYRO OutputDataRate = %d\r\n\n", gyroConfig.odr);
    1009.     }
    1010.     else
    1011.     {
    1012.         printf("Failed to get gyroscope configuration\r\n");
    1013.         failures++;
    1014.     }
    1015.  
    1016.  
    1017.     /////////////////////////////////////////////////////////////////////////////////////
    1018.  
    1019.  
    1020.  
    1021.     fivesecs_tick.attach(&LevelResetFunc,5.0);
    1022.  
    1023.  
    1024.  
    1025.     levelmon_tick.attach(&MonitorLevel,GYRO_SCAN_INTERVAL);    // GYRO SCAN INTERVAL
    1026.  
    1027.     ReadConfig();
    1028.  
    1029.     while (1) {
    1030.         if (++i == 1000) {
    1031.            i = 0;
    1032.         }    
    1033.         ble.waitForEvent();
    1034.         //int relay_data = GetRelayData();
    1035.         //sr.write(relay_data);
    1036.     } // while(1)
    1037. }
    1038.  
    1039.  
    1040.  
    1041.  
  2. I had mentioned in an earlier blog that I was using a couple of 74HC4067s to expand my data acquisition system to 32 input lines and 4 MAX7219s to display this data with the help of 32 7-segment LED displays . In this blog, I'll elaborate on the programming that went into making this happen.
    The 74HC4067 is a high-speed, 16-channel Analog Multiplexer with 4 control inputs to select any 1 of 16 channels and an Enable pin that can be used to choose (a seriously large amount of time could have been saved, in my case, had I only glanced at the datasheet and realized that the EN printed on the breakout board was, in fact, active LOW and had a bar drawn over the letters :( ) between the multiplexers.
    adcmux.jpg
    While the inputs can be computed so that code is minimized, hard coding these values would be much faster to execute. Also, the inputs are fixed, so no substantial gain in keeping them configurable. The patterns to be shifted into the register to select channel 0 to 31 now become

    Code (Text):
    1.  
    2. unsigned char ADC_SELECT[32] = {32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
    3. 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
    4.  
    I don't really need the entire system to be monitored every second, so a simple round-robin scheduling of these scans takes the load off the ADC by requesting a subset of characteristics to be read (and displayed) every second.
    Code (Text):
    1.  
    2.     int selected_disp = mc_seconds % 6;
    3.     switch (selected_disp)
    4.     {
    5.     case 0:
    6.         //Light
    7.         ScanLight();
    8.         break;
    9.     case 1:
    10.         // Humidity Temperature
    11.         ScanHumidity();
    12.         break;
    13.     case 2:
    14.         // Oxygen
    15.         ScanOxygen();
    16.         break;
    17.     case 3:
    18.         //CO2
    19.         ScanCO2();
    20.         break;
    21.     case 4:
    22.         //pH
    23.         ScanpH();
    24.         break;
    25.     case 5:
    26.         //Water Level
    27.         ScanWaterLevel();
    28.         break;
    29.     }
    30.  
    (The DHT-11 has it's own digital protocol to receive temperature and humidity values that it senses. I'm using a library contributed by Eric Fossum to interact with these sensors. Also, the MH-Z14 CO2 sensor has both a digital readout as well as an analog interface. Since the accuracy isn't critical in this case, and also I'm saving up pins that I know I will need later for the Level and Stability Monitor, I opted for the analog mode).

    The MAX7219 ensures that no effort is wasted by the ARM processor in maintaining the signals needed to keep the display alive and flicker-free. 8 7-segment displays per tray, and a set of 8 displays for the system clock would require an absurd number of GPIO pins for the 32 displays and make this project impossible to implement otherwise.

    The MAX7219 operates in two stages - Initalization and Display. Any communication with this device requires two bytes of data - Register Address and Data to be written into that register. The DataOut pin on the 7219 acts as a Serial Out and makes the data at DataIn available at this pin, 8 clock cycles later. This allows us to cascade the 4 displays and a modification of the SPI write function (SPI_Write8) allows us to now write 8 bytes (2 per display) into all 4, as if they were a single display system. Since data from the sensors is read one by one (since a single ADC is being used), it would be quite wasteful to keep trying to update the display as and when data is available. A better approach would be to store the data and accumulate it till there is enough to display on all 4 display units.

    Code (Text):
    1.  
    2. //Write into the display buffer    
    3. void SPI_WriteToBuf(int bufslot, int digit, unsigned char MSB, unsigned char LSB)
    4. {
    5.        BUFFER_MSB[bufslot][digit] = MSB;            
    6.        BUFFER_LSB[bufslot][digit] = LSB;
    7.    
    8. }
    9.  
    and then call the appropriate function to send out the data to be displayed.

    Code (Text):
    1.  
    2. /// Send eight bytes on SPI bus to 4 7219's
    3. void SPI_Write8BUF()
    4. {
    5.     for(int digit=0;digit<8;digit++)
    6.     {
    7.         ClearLoadPin();
    8.  
    9.             spi.write(BUFFER_MSB[3][digit]);                 // Send two bytes to 3
    10.             spi.write(BUFFER_LSB[3][digit]);
    11.             spi.write(BUFFER_MSB[2][digit]);                 // Send two bytes to 2
    12.             spi.write(BUFFER_LSB[2][digit]);
    13.             spi.write(BUFFER_MSB[1][digit]);                 // Send two bytes to 1
    14.             spi.write(BUFFER_LSB[1][digit]);
    15.             spi.write(BUFFER_MSB[0][digit]);                 // Send two bytes to 0
    16.             spi.write(BUFFER_LSB[0][digit]);
    17.  
    18.             SetLoadPin();
    19.     }
    20. }
    21.  
    (A practical tip for anyone replicating this system - when you line up 8 7-segments and want an easy way to plug them in and out, use two 40-pin IC bases on your PCB, side by side. Switching out a defective display becomes effortless)

    Like most SPI projects, it was impossible to get this one to work without spending hours staring at the Logic Analyzer traces. So many mistakes just waiting to happen when there are 4 displays on a single bus.

    A small side-note on the Stability Monitor. The way I've designed it, the system waits for any disturbance and detects which side or corner has been compromised. I've used 4 dual color (RED-GREEN) LEDs to represent the corners. All GREEN would mean that everything is fine, RED on a single LED implying a corner was affected, RED on two LEDs implying the side connecting them was compromised. Once the disturbance has been detected, the system stays in that state, issuing an alert, till the RESET button (in the middle) is pressed, returning it to a stable (ALL GREEN) state. I've written the code to ensure what, I felt, was a minimum number of comparisons to arrive at an evaluation as quickly as possible (resulting in the code looking clumsy and obfuscated).

    The basic logic behind obtaining the displacement (angular) from the gyroscope readings would be to integrate the angular velocity over time to arrive at the angular displacement. I am not worrying too much about the magnitude of displacement right now, so the accuracy isn't important. It is only important to ascertain whether or not there was an incident.

    Code (Text):
    1.  
    2. #define    GYRO_TRIGGER            5.0   // basically sets the sensitivity of the system
    3. #define GYRO_SCAN_INTERVAL_MS    200         // smaller interval would mean a more accurate integral of angular velocity
    4. [/CODE}
    5.  
    6. 200ms should be small enough an interval to detect any disturbance to the unit and large enough to allow all other operations to be carried out comfortably.
    7.  
    8. [CODE]
    9.     while (1) {
    10.    
    11.         imu.getGyroAccXYZandSensorTime(accData, gyroData, sensorTime, accConfig.range, gyroConfig.range);
    12.        imu.getTemperature(&imuTemperature);
    13.    
    14.         if(!disturbed)
    15.         {
    16.        
    17.            //    QUADRANTS
    18.            //
    19.            //     2     1
    20.            //
    21.            //     3     4
    22.        
    23.            int xval = gyroData.xAxis.scaled;
    24.            int yval = gyroData.yAxis.scaled;
    25.            int zval = gyroData.zAxis.scaled;
    26.        
    27.            if((QUAD1!=0)||(QUAD2!=0)||(QUAD3!=0)||(QUAD4!=0))
    28.                 disturbed = 1;
    29.        
    30.            if(xval > GYRO_TRIGGER)  // 3 or 4 lowered
    31.               if(yval > GYRO_TRIGGER) // 4
    32.                   QUAD4 = 1;
    33.               else if(yval < -(GYRO_TRIGGER))  //3
    34.                   QUAD3 = 1;
    35.               else // 3 and 4
    36.                   QUAD3 = QUAD4 = 1;
    37.            else if(xval < -(GYRO_TRIGGER))  // 1 or 2 lowered
    38.               if(yval > GYRO_TRIGGER) // 1
    39.                   QUAD1 = 1;
    40.               else if(yval < -(GYRO_TRIGGER))  //2
    41.                   QUAD2 = 1;
    42.               else
    43.                   QUAD1 = QUAD2 = 1;
    44.            else if(yval > GYRO_TRIGGER) // 1 and 4
    45.               QUAD1 = QUAD4 = 1;
    46.            else if(yval < -(GYRO_TRIGGER)) // 2 and 3
    47.               QUAD2 = QUAD3 = 1;
    48.            // else // basically, all is well - no disturbance
    49.            //    QUAD1 = QUAD2 = QUAD3 = QUAD4 = 0;
    50.         }  // if(!disturbed)
    51.        wait_ms(GYRO_SCAN_INTERVAL_MS);
    52.    
    53.     } // while(1)
    54. }
    55.  
    Below is a picture of the board with the LEDs and the RESET button. I can't afford 8 output pins for the LEDs, so it's back to Ollie's ShiftOut library and my stash of 164's.
    StabMonSmall.jpg
  3. This phase in the project seems to be progressing slowly - probably because the activity I find myself involved in is very "non-electronic". I ended up riding across town to a distributor's warehouse and found myself a cylinder of CO2.

    co2cylinder.jpg
    It's a refillable canister of CO2 used in a table-top soda making machine called "Mr. Butler's". Now I need to figure out how to safely control the release of the CO2 whenever required. Probably some welding and lathe work will be involved.

    I also picked up an inexpensive roll of acrylic material - thin enough to see the full unit lighting up with the 6500 Kelvin florescent glow but thick enough to reflect a lot of that light back on to the plants inside. And a few strips of Sal wood to create a door frame.

    acrylicwoodsmall.jpg
    Tomorrow is beginning to look like a day of intense cardio... :)

    Something that's gnawing at the back of all my thoughts is the fact that I haven't come up with a design for level adjustment in this unit. The idea is to use the gyro to inform the user if the entire system is standing on a level surface or not. This is important because there are relatively shallow trays of nutrient solution placed inside the unit and keeping the system level and stable is a priority. The simplest solution that came to me was a set of 4 LEDs indicating the four corners of the unit. A blinking LED (or two) would indicate which corner(s) need(s) to be raised or lowered. I hope I can take some time off and finish this tomorrow. Really looking forward to seeing this feature come alive.

    The absolute highlight of the day, however, was the sight of this beautiful screen :

    blescreensmall.png

    After some three hours of frustrating build error messages
    (actually it was this message repeating over and over again, no matter what I tried :

    [mbed] Updating library "mbed" to rev #833480836776
    fatal: reference is not a tree: 83348083677696a51d9b0250308e88815d77f10a
    [mbed] ERROR: Unable to update "mbed" to rev #833480836776)

    I finally managed to successfully compile the demo. The HYGROMAX farm turning up on the BLE Scanner app is probably the most beautiful thing I've seen in a long time. It is so hard to keep from staring at the screen every now and then... Thanks for the work on the BLE branch which made this possible, Maxim.

    p.s. Quite frankly, I'm not very good at this build stuff, primarily since I've not paid much attention to it - so the errors I hit were probably just me doing something wrong or a simple error in the instructions online. Most of the setup on my laptop, as far the mbed platform goes, is mostly a result of watching videos on YouTube. But if this is an error that everyone else is also running into, the thing that worked for me is downloading the entire branch of the mbed os and copying it into the corrupted folder once the build fails halfway (after "mbed import" and "mbed compile"). Hope this helps... the joy of seeing the FTHR board turn up on the scanner is quite something else !
  4. Even though Rob kept insisting we start documenting our work, I figured I had time and I'd do it in the end and what resulted was a very painful last-minute rush yesterday. Thankfully, there's a 1-week extension and I plan to update this blog on a daily basis, starting today.
    My name is Aashish. My idea for this competition is a low-cost, digitally controlled, Personal Hydroponics Farm. Hydroponics is the science of growing plants without soil (directly nourishing them through nutrient solution) and what makes this a "Personal" farm is that it is portable, small enough to fit inside a kitchen or a bedroom and designed to plug directly into a regular wall socket found in our homes. I've also decided to call it the HYGROMAX 630 since it's a 630 liter unit (but mostly in the hope that having both "MAX" and "630" in the name might attract more points :) ).
    I'm going to write this blog with a view that it might help anyone who might be inspired to try building this on their own. So the information may be unnecessarily detailed and there might be ramblings about decisions that I took and why I took them.
    Below is a side-by-side comparison of the design sketch I came up with right at the start of the competition and a picture of how the unit finally looks. A few features had to be modified (removed) for various reasons (for starters, the load cells weighing the plants seemed to be overkill, even though the MAX32630 seems to have no problems taking on any additional tasks you throw at it).

    sidebysidesmall.jpg

    It will, eventually, have white, acrylic walls and a door to seal the unit, minimizing any threat to the growing plants (3 trays of nutrient solution, each measuring 100cm x 100cm x 21cm and a total of 48 plants - each level housing 16 (4x4) plants).

    A pictorial BOM of the equipment that is actually being controlled by the electronics is given below.

    picBOMsmall.jpg

    3 Humidity/Temperature Sensors - DHT-11s (I seem to have misplaced one), a pH electrode (need to pick up two more, but the reviews on Amazon for this one were suspicious, so I'll try it out first), 9 LDRs (3 per tray), an MH-Z14 CO2 sensor (was going to be 3 of them, but at those prices, my idea would no longer qualify as 'low-cost' ), an aquarium pump with tubing (for oxygen) and air stones (to enhance the amount of dissolved oxygen) and a set of T5 LED lights (the main source of light is going to be fluorescent, but this system will support configurable profiles such as "low power" in times of electrical failure, when backup power is being used - these LED lights would kick in at that time. Also included are 3 fans - mainly to circulate the CO2, when released into the system and a secondary role would be to serve as dehumidifying agents, when used with vents.
    What is missing is a CO2 cylinder which I'm having trouble sourcing. The prices online are frightfully intimidating and there are a whole lot of local vendors willing to 'refill' my CO2 cylinder at a very small cost, if I have an empty one. Might have to resort to a CO2 boost bucket (synthesize CO2 with chemicals and pipe that into the unit) - but that is my last resort.

    I wish the next picture was as pretty as the previous one. But it isn't. It's my workbench with most of the hardware built.

    junglesmall.jpg

    The downside to the FTHR board offering such awesome features like SD card, Bluetooth, PMIC, Gyro, etc. is that only a few pins are available to use as GPIO. But with a processor this fast, multiplexing a whole lot of these signals becomes a very comfortable option. Two 74HC4067s open up 32 lines to be used for data acquisition and to further reduce the need for pins, a 74LS164 shift register is used to generate the 6 control lines needed to work the multiplexers. 3 8-channel relay boards provide 24 lines for the various actuators and here, again, the pins are minimized by using three shift registers.
    Thanks to Ollie Milton for the 'ShiftOut' library on mbed. It cut down hours of downtime that I would certainly have encountered if I had gone at it alone. I know I should be using shift registers with latches on the relays, but I had a bunch of 164s lying around and was in a bit of a rush. The final design will have them, of course - that is also the reason I'm living with a jungle of wires and not getting these PCBs printed just yet. But that is not to say that I haven't done anything about the wires. A lot of time and solder has gone into getting pretty close to the real thing.

    manualPCBsmall.jpg

    This approach did not, however, work with the 4 MAX7219s that I'm using to control the 32 7-segment displays to monitor the various parameters like humidity, temperature, pH, light etc. The wiring turned into something really nasty and debugging the problems on that started becoming pure rocket science (No offence, Dan Julio... :) if you're reading this, Dan, please know that I am insanely impressed by your project ). I eventually got the PCBs printed.

    printedPCBsmall.jpg

    Thanks to the team at Maxim for the MAX7219 library. For someone like me, who's always been accustomed to building everything from scratch and re-inventing the wheel a lot, it is a whole new, refreshing experience to see the kind of contribution that is being made on the mbed platform. Libraries that can be picked up and plugged right into any project and they just work seamlessly, right off the bat. I'm so looking forward to doing my bit of contributing as well :)
    The unit is configured through the memory card. The format of the file is continuously evolving. There was a huge dependence on the file since I wasn't able to get the BLE to work even after a ton of effort. The ideal thing would be to have an android app managing the whole configuration and leave the SD card for data archival/storage. I hope there's enough time for me to try this out, especially since there now seems to be hope in the BLE department. It would also be a lot of fun to be able to log this data into the cloud and retrieve a report from anywhere around the world (also because, in my intial enthusiasm, I ended up buying the domain - hygromax.com for this purpose).

    On a final note, here's a picture of my 3-week old babies eagerly awaiting the completion of this project, the completion of their new home...

    lettucesmall.jpg

    You can't make out clearly from the picture, but they're extremely grateful to Maxim Integrated and AllAboutCircuits.com for giving them this opportunity to embark on this awesome journey of Life.

    Also, they can't seem to shut up about that Hololens.