QCA402X based Smoke Detector

Skill LevelArea of FocusOperating SystemPlatform/Hardware
IntermediateBluetooth, IoT, Sensors, Smart HomeRTOSDragonBoard 410c, QCA 402x WiFi/BLE/ZigBee

This project is designed to interface the QCA4020 development board with a smoke detector and DragonBoard™ 410c development platform from Arrow Electronics.

The main objective of this project is to use the QCA4020 development board to detect smoke in a home environment and generate alerts in case of emergencies.

This demo is based on the QCLI_demo available in the QCA4020 SDK. In this demo, there is a Peripherals module which includes code to read ADC data from various sensors on the development board. The main idea of this smoke alarm is to use the peripheral module in QCLI_demo, and use its ADC interface to read the value of the smoke sensor. If the value of the smoke alarm is higher than a certain voltage range, set the voltage value of a GPIO on the board to a high level.

1. Modifying the QCLI_demo settings

By default, the peripherals module is not enabled in the QCLI_demo application, so code modification is required to open and use the peripherals module. The module is opened as follows:

1.1 Modify build.bat
Modify build.bat found at the following path:

<path>/target/quartz/demo/QCLI_demo/build/gcc/build.bat
Change
IF /I "%CFG_FEATURE_PERIPHERALS%" == "" (SET CFG_FEATURE_PERIPHERALS=false)
To
IF /I "%CFG_FEATURE_PERIPHERALS%" == "" (SET CFG_FEATURE_PERIPHERALS=true)

1.2 Modify env.config
Modify env.config found at the following path:

<path>/target/quartz/demo/QCLI_demo/build/gcc/env.config
Change
CFG_FEATURE_PERIPHERALS=false
To
CFG_FEATURE_PERIPHERALS=true

1.3 Build and Flash
Follow instructions in the QCLI_demo to build and flash the application on the QCA4020 development board. After programming, remove the programming jumper, reset the board and open the serial port and the following directory appears:

Startup CLI menu on the QCA4020 development board

The "16. Peripherals" option seen above shows that the compilation was successful.

2. Read the ADC data value of the smoke sensor

2.1 Connections

Smoke Sensor PinQCA4020 Development Kit Pin
1 Vcc (+5V)Jumper J1 pin 2 5V
2 GroundJumper J1 Pin 7 AGND
3 Digital OutNot Connected
4 Analog OutJumper J2 Pin 5 Analog Sense

Connecting the smoke detector with the QCA4020 development board

2.2 Single reading of ADC data
The value of the sensor can be read directly by using the command, by entering "16", and the resulting display interface is as follows:

Peripherals menu on the QCA4020 QCLI_demo

Enter command "4" to read the smoke sensor data as shown:

Reading ADC values from the smoke sensor

This method of operation can only read the data of the ADC once, and cannot read the data of the ADC continuously, so the code needs to be modified accordingly.

2.3 Continuous reading of ADC data
The continuous reading of values can be realized by the continuous instruction input. Queried in the file qal.cThe following thread implements the instruction for receiving and processing. QCLI_Thread The following function is responsible for receiving and processing related instructions. QCLI_Process_Input_Data() Therefore, the modification is mainly performed in this thread, and the modifications are as follows:

First, the following function will be encapsulated, and the data read by the ADC demo will be put into the thread, and the interface for the future smoke value judgment will be written. QCLI_AdcThe modifications are as follows:

Modify in adc_demo.cadd interface:Qcli_Adc Add print interface

uint32_t Qcli_Adc()
{
   qapi_ADC_Read_Result_t *result;
uint32_t i;
//for (i=0; i < MAX_CHANNELS; i++)
   //{
           result = &chan_result[7].chan_result;
           QCLI_Printf(qcli_adc_group, "ADC[7] = %ddmV \n", result->microvolts/1000);
           return result->microvolts/1000;
   //}
}
//Add a print interface:
void printf_a(uint32_t result_adc)
{
   QCLI_Printf(qcli_adc_group, "ADC = %ddmV \n", result_adc);
}

Add an interface function in adc_demo.h and reference it in pal.c

        diff --git a/src/adc/adc_demo.h b/src/adc/adc_demo.h
        index 60d9812..26e2869 100644
        --- a/src/adc/adc_demo.h
        +++ b/src/adc/adc_demo.h
        @@ -15,6 +15,8 @@ typedef struct _adc_result {
         } adc_result_t;

         extern adc_result_t chan_result[MAX_CHANNELS];
        +extern uint32_t Qcli_Adc();
        +extern void printf_a(uint32_t result_adc);

         /**
            @brief This function registers the ADC demo commands with QCLI.
        diff --git a/src/qcli/pal.c b/src/qcli/pal.c
        index f8c833d..bd18e6c 100644
        --- a/src/qcli/pal.c
        +++ b/src/qcli/pal.c
        @@ -45,6 +45,7 @@
         #include "ecosystem_demo.h"
         #include "json_demo.h"
         #include "kpi_demo.h"
        +#include "adc_demo.h"
         #ifdef CONFIG_QMESH_DEMO
         #include "qmesh_demo_menu.h"
    

A while loop, such as shown below, can be used to continuously read the smoke sensor value every 200ms:

        while(1) {
          QCLI_Process_Input_Data(2, "16");
          Process_Command();
          clear_buffer();
          //QCLI_Process_Input_Data(1, " ");
          QCLI_Process_Input_Data(1, "4");
          Process_Command();
          //QCLI_Process_Input_Data(1, " ");
          QCLI_Process_Input_Data(1, "4");
          Process_Command();
          ADC=Qcli_Adc();
          printf_a(ADC);
          //QCLI_Process_Input_Data(1, " ");
          QCLI_Process_Input_Data(1, "3");
          Process_Command();
          qurt_thread_sleep(200);
          };
    

2.4 Control the high and low levels of GPIO by the value of the smoke sensor
The ADC value read for the smoke sensor is generally 850 mv, so this value is set to be the alarm threshold. GPIO4 is set to high if the value is above this threshold and low if the value is below this threshold. The code is as follows:

        if(ADC > 850)
        {
        QCLI_Process_Input_Data(1, "3");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(2, "16");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(2, "11");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(20, "gpio_tlmm 4 10 1 2 1");
        Process_Command();
        clear_buffer();
        } else {
        QCLI_Process_Input_Data(1, "3");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(2, "16");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(2, "11");
        Process_Command();
        clear_buffer();
        QCLI_Process_Input_Data(20, "gpio_tlmm 4 10 1 1 0");
        Process_Command();
        clear_buffer();
        }
    

3. Receiving data on DragonBoard 410c

The QCA4020 monitors the status of the smoke sensor. When the monitored value is greater than a certain threshold, the level value of GPIO is set to indicate that an event has occurred.

The below steps aim to achieve:

  1. The Dragonboard 410c development board monitors the state change of the GPIO port to which the QCA4020 is connected, thereby generating an interrupt.
  2. Provide a node for the upper layer to view the status of the smoke sensor. One of them indicates that an event has occurred. You can set the node value to 0 by writing to this node.

3.1 Connection Diagram
Connecting the QCA4020 board to the DragonBoard 410c

Configure the device tree
Add node on the DragonBoard 410c at the following location:

        /*arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi*/

        qca4020 {
                    compatible = "linux,QCA4020";
                    interrupt-parent = <&msmgpio>;
                    interrupts = <0 36 2>;
                    gpio_int = <&msmgpio 36 0>;
        };
    

3.2 Configuring Makefile
Location of the Makefile:

*/drivers/misc/Makefile*/

Add the following line:

obj-y += qca4020.o

3.3 Drive Implementation

        #include <linux/module.h>
        #include <linux/kernel.h>
        #include <linux/init.h>
        #include <linux/sysfs.h>
        #include <linux/delay.h>
        #include <linux/platform_device.h>
        #include <linux/err.h>
        #include <linux/device.h>
        #include <linux/interrupt.h>
        #include <linux/irq.h>
        #include <linux/of_gpio.h>
        #include <asm/uaccess.h>
        #include <linux/kdev_t.h>
        #include <linux/slab.h>
        #include <linux/workqueue.h>
        struct qca4020_data {
            struct platform_device *pdev;
            int flag;
            int gpio_int;
            int irq;
        };
        static struct qca4020_data* data;
        static irqreturn_t qca4020_interrupt_handler(int irq, void *ptr)
        {
            data->flag = 1;
            return IRQ_HANDLED;
        }
        static ssize_t qca4020_value_store(struct device *dev, struct device_attribute* attr,
                                                    const char *buf, size_t len)
        {
            data->flag = 0;
            return len;
        }
        static ssize_t qca4020_value_show(struct device *dev, struct device_attribute* attr,
                                                    char *buf)
        {
            ssize_t ret = sprintf(buf, "%d\n", data->flag);
            return ret;
        }
        static DEVICE_ATTR(value, 0664, qca4020_value_show, qca4020_value_store);
        static int QCA4020_probe(struct platform_device *pdev)
        {
            int result;
            struct device_node* node = pdev->dev.of_node;
            printk("qca4020 probe enter\n");
            data = devm_kzalloc(&pdev->dev, sizeof(struct qca4020_data), GFP_KERNEL);
            if (!data) {
                pr_err("%s kzalloc error\n", __FUNCTION__);
                return -ENOMEM;
            }
            dev_set_drvdata(&pdev->dev, data);
            data->gpio_int = of_get_named_gpio(node, "gpio_int", 0);
            if (!gpio_is_valid(data->gpio_int)) {
                pr_err("gpio_int not specified\n");
                goto err;
            } else {
                result = gpio_request(data->gpio_int, "qca_gpio");
                if (result < 0) {
                    pr_err("Unable to request qca_gpio\n");
                    goto err;
                } else {
                    gpio_direction_input(data->gpio_int);
                }
            }
            data->irq = gpio_to_irq(data->gpio_int);
            result = request_irq(data->irq, qca4020_interrupt_handler,
                                                         IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "qca4020_intr", data);
            if (result < 0) {
                pr_err("Unable to request irq\n");
                goto err;
            }
            result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_value.attr);
            if (result < 0) {
                printk("sysfs create file failed\n");
                goto err;
            }
            printk(KERN_INFO "QCA4020 probe success\n");
            return 0;
            err:
            kfree(data);
            printk(KERN_ERR "QCA4020 probe failed\n");
            return -EINVAL;
        }
        static int QCA4020_remove(struct platform_device *pdev)
        {
                gpio_free(data->gpio_int);
                kfree(data);

                return 0;
        }
        static struct of_device_id mach_match_table[] = {
            { .compatible = "linux,QCA4020"},
            { },
        };
        static struct platform_driver QCA4020_driver = {
            .probe = QCA4020_probe,
            .remove = QCA4020_remove,
            .driver = {
                .owner = THIS_MODULE,
                .name = "QCA4020GPIO",
                .of_match_table = mach_match_table,
            },
        };
        module_platform_driver(QCA4020_driver);
        MODULE_AUTHOR("[email protected]");
        MODULE_LICENSE("GPL");
    

Now that you understand how the code is implemented, follow the below steps to build the project.

  1. Download the repository from the GitHub link provided above.
  2. Build and flash the QCA4020 development board with the QCA4020 application.
  3. Set up the DragonBoard 410c, and make all the required software changes.
  4. Connect the smoke detector to the QCA4020 development board as shown above
  5. Connect the QCA4020 development board to the DragonBoard 410c
  6. Turn on the QCA4020 board and the DragonBoard 410c

The QCA4020 board is designed to read the signal from the smoke detector. If the value is above a threshold, the board sends the signal to the DragonBoard 410c via the GPIO connection. The DragonBoard 410c is programmed to reads the change in GPIO value and trigger an alert.

NameCompany
ScottThundersoft
QiliangThundersoft