CW32L083智能温湿度监控系统

描述

【系统功能】

CW32l083为主控制的无线终端数据收发。运行国产RT-Thread操作系统。主要功能为实现用E31-TTL-50接收各个模块发送上来的数据,解析数据,分析数据,显示数据,并实现信息的显示,以及异常情况的显示、警告功能。

无线终端主要以cw32L031为主控,采集sht30温湿度传感器数据,通过E31-TTL-50无线模块将数据上传。实现5微安的待机电流的超低功耗。

【功能模块】

主机:

1、接收模块:接收无线终端模块发送的温湿度数据,解析数据后,更新模块数据。

2、巡检模块:定时巡检各个无线终端的数据,判定工作状况、更新显示、报警标志。

3、显示模块:根据各个模块的工作状态,生成为示数据,用TFT屏展示。

4、报警模块:驱动pwm模块,装载pwm重载值,发出警示声音。

无线终端:

1、温湿采集模块:采集sht30数据。

2、发送模块:将数据打包,通过无线发送。

3、休眠模块:发送完数据后进入深度休眠状态,由AWT模块定时唤醒。

【硬件】

主机:

1、CW32L083VxTx StartKit REV01开发板。

2、ST7735TFT显示屏。

3、E31-TTL-50无线串口模块。

无线终端:

1、cw32l031开发板

2、Sht30温湿度传感器。

3、E31-TTL-50无线串口模块。

4、可充电锂电池。

【开发环境】

1、代码编译环境采集ubuntu20.4;

2、代码编辑工具为vscode 1.79.2;

3、交叉编译器为arm-nano-eabi-gcc;

4、固件库为cw32提供的固件库;

5、gcc启动文件与链接由作者在cortex-M0+的其他软件上移植过来;

6、下载器为CW32配送的wch-link;

7、代码下载软件为pyocd;

8、调式工具为gdb。

本次开发板的编译环境、工具均采用开源工具。

【操作系统】

本工程的主控,作者移植了RTT-Thread Nano 3.15版本。RTT作为一款国产开源免费的操作系统可以提供强大的功能,为CW32的性能发挥提供强力的支持。

【程序流程图】

1、主机端由RTT开启两个主要任务,用于数据显示与巡检,同时利用串口中断来实时处理接收的数据。GTIM定时开启PWM任务,来驱动开发板板载的BEEP。流程图如下:

RT-Thread

2、无线终端采用单线流程,主要是采集数据后进入休眠,做到极简才能实现最好的功耗控制。流程图如下:

RT-Thread

【原理图】

1、无线端终采集:

RT-Thread

2、主机端:

RT-Thread

【程序设计】

一、无线采集端

1.IIC初始化,采用模拟I2C主要代码是对

二、主机端

1.主机端我们处理数据的核心为sht30数据,声明结构体如下:

typedef struct _sht30_data

{

uint32_t ID;

int temp; //温度值

int temp_upper_limit; //温度值上限

int temp_lower_limit; //温度值下限

uint16_t humi; //湿度

uint16_t humi_upper_limit; //湿度上限

uint16_t humi_lower_limit; //湿度上限

uint32_t  time_tick;      //更新数据计时

enum _sht30_errcode sht_errcode;

} SHT30_infor;

主要用于存储数据的核心,以后所有的任务都是针对这个模块进行。

2.同时声明一个枚举,来确定测量点的状态:

enum _sht30_errcode{

NORMAL=0,

ABNORMAL,

OFFLINE,

};

3.先约定好默的一些参数,最大传感器个数,温湿度报警上下限,巡检次数初值:

#define maxID 2

#define MaxTime 300

#define HUMI_LOWER 500

#define HUMI_UPPER 750

#define TMPE_LOWER 100

#define TMPE_UPPER 300

到此我们的数据结构设计完成。

4.时钟的初始化,由于主机端需要高速处理数据这里配置为64MHz:

void RCC_cofiguration(void)

{

RCC_HSI_Enable(RCC_HSIOSC_DIV6);



// 使能PLL,通过HSI倍频到 64MHz

RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8); //HSI 默输出8MHz

///< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle

///< 当使用的时钟源HCLK大于48M,小于等于72MHz:设置FLASH 读等待周期为3 cycle

__RCC_FLASH_CLK_ENABLE();

FLASH_SetLatency(FLASH_Latency_3);

//时钟切换到PLL

RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);

RCC_SystemCoreClockUpdate(64000000);

}

5.主机端的无线接收使用了uart1,端口选择了PE8与PE9作为TXD、RXD,初始化代码为:

void E31_UART_Init(void)

{

uint32_t PCLK_Freq;

GPIO_InitTypeDef GPIO_InitStructure = {0};

UART_InitTypeDef UART_InitStructure = {0};



PCLK_Freq = SystemCoreClock > > pow2_table[CW_SYSCTRL- >CR0_f.HCLKPRS];

PCLK_Freq > >= pow2_table[CW_SYSCTRL- >CR0_f.PCLKPRS];

// 调试串口使用UART3

//  PA8- >TX

//  PA9< -RX

// 时钟使能

RCC_AHBPeriphClk_Enable(E31_UART_GPIO_CLK, ENABLE);

E31_UART_APBClkENx(E31_UART_CLK, ENABLE);



// 先设置UART TX RX 复用,后设置GPIO的属性,避免口线上出现毛刺

E31_UART_AFTX;

E31_UART_AFRX;

GPIO_InitStructure.Pins = E31_UART_TX_GPIO_PIN;

GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_Init(E31_UART_TX_GPIO_PORT, &GPIO_InitStructure);

GPIO_InitStructure.Pins = E31_UART_RX_GPIO_PIN;

GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;

GPIO_Init(E31_UART_RX_GPIO_PORT, &GPIO_InitStructure); 



UART_InitStructure.UART_BaudRate = E31_UART_BaudRate;

UART_InitStructure.UART_Over = UART_Over_16;

UART_InitStructure.UART_Source = UART_Source_PCLK;

UART_InitStructure.UART_UclkFreq = PCLK_Freq;

UART_InitStructure.UART_StartBit = UART_StartBit_FE;

UART_InitStructure.UART_StopBits = UART_StopBits_1;

UART_InitStructure.UART_Parity = UART_Parity_No ;

UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;

UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;

UART_Init(E31_UARTx, &UART_InitStructure);    

//优先级,无优先级分组

NVIC_SetPriority(E31_UART_IRQ, 0);

//UARTx中断使能

NVIC_EnableIRQ(E31_UART_IRQ);

}

同时配置中断函数,主要功能是判断是否接到了帧属,如果接收到帧尾则把数据交给回调函数进行处理,代码如下:

void UART1_UART4_IRQHandler(void)

{

/* USER CODE BEGIN */

uint8_t TxRxBuffer;

if(UART_GetITStatus(CW_UART1, UART_IT_RC) != RESET)

{

    TxRxBuffer = UART_ReceiveData_8bit(CW_UART1);

    

    if(e31_rx_cnt < E31_RX_MAXLEN)

    {

        if ((TxRxBuffer == 0x0A) && (e31_rx_state == 1))

        {

            e31_rx_state = 2;

            e31_exp_data();

        }

        else if ((TxRxBuffer == 0x0D) && (e31_rx_state == 0))

        {

            e31_rx_state = 1;

        }

        else if (e31_rx_state == 0)

        {

            e31_rx_buff[e31_rx_cnt] = TxRxBuffer;

            e31_rx_cnt ++;

        }

        

    }

    else

    {

        e31_rx_cnt = 0;

        e31_rx_state = 0;

    }

    UART_ClearITPendingBit(CW_UART1, UART_IT_RC);

}

/* USER CODE END */

}

同时回调函数,为处理与解析数据更新到sht30数据之中:

void e31_exp_data(void)

{

int temp;

uint16_t humi;

uint32_t ID;

if(e31_rx_state == 2)

{

    if(e31_rx_cnt == 14)

    {

        temp = e31_rx_buff[10]< < 8 | e31_rx_buff[11];

        humi = e31_rx_buff[12]< < 8 | e31_rx_buff[14];

        ID =  e31_rx_buff[6]< < 24 | e31_rx_buff[7]< < 16 | e31_rx_buff[8]< < 8 | e31_rx_buff[9];

        updata_sht30(temp, humi, ID);

        rt_kprintf("ID:%X, temp:%d, humi:%d\\r\\n", ID, temp, humi);

    }

}

e31_rx_cnt = 0;

e31_rx_state = 0;

}

6.ST7735的驱动,驱动采集模拟SPI进行驱动,详细的驱动见工程源码包。

7.PWM驱动,pwm选用PA6为pwm输出端,初始化为1KHz的输出来驱动板载的蜂鸣器。在初始化驱动后,我们装载最大的装截时,占空比为100%,使得蜂鸣器停止,在后面的需要输入报警声后,调整为50%的占空比,来实现蜂鸣器的报警声:

void init_beep(void)

{

GTIM_InitTypeDef GTIM_InitStruct = {0};



__RCC_GTIM1_CLK_ENABLE();   // GTIM2时钟使能

/* PA6 PWM 输出 */

__RCC_GPIOA_CLK_ENABLE();

PA06_AFx_GTIM1CH1();

PA06_DIR_OUTPUT();

PA06_DIGTAL_ENABLE();

    





GTIM_InitStruct.Mode = GTIM_MODE_TIME;

GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;

GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV2;

// GTIM_InitStruct.ReloadValue = 60100UL - 1;    // PWM频率为 48M/60100=800Hz, SPWM周期 = 800/2/1000= 0.4Hz

GTIM_InitStruct.ReloadValue = 32000UL - 1;    // PWM频率为 64M/2/64000=1000Hz, SPWM周期 = 800/2/1000= 0.4Hz

GTIM_InitStruct.ToggleOutState = DISABLE;



GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct);

GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);

GTIM_SetCompare1(CW_GTIM1, 32000-1);

GTIM_Cmd(CW_GTIM1, ENABLE);

}

void alarm_ON(void)

{

GTIM_SetCompare1(CW_GTIM1, 16000-1);;

}

void alarm_OFF(void)

{

GTIM_SetCompare1(CW_GTIM1, 32000-1);

}

8.按照程序流程图,我们创建了两个任务,一个为巡检任务来实现对传感器模块的数据监控,并实理更新工作状态,代码如下:

/* 巡检任务 */

void thread_sht30_check_entry(void *parameter)

{

int i;

uint8_t alarm_sta;

while(1)

{   alarm_sta = 0;

    for(i=0; i< maxID; i++)

    {

        if(sht30[i].time_tick == 0)

        {

            //发送离线的警告

            sht30[i].sht_errcode = OFFLINE;

            sht30[i].temp = 0;

            sht30[i].humi = 0;

            alarm_sta ++;

        }

        else if (sht30[i].temp < sht30[i].temp_lower_limit \\

                 || sht30[i].temp > sht30[i].temp_upper_limit \\

                 || sht30[i].humi < sht30[i].humi_lower_limit \\

                 || sht30[i].humi > sht30[i].humi_upper_limit )

        {

            sht30[i].sht_errcode = ABNORMAL;

            sht30[i].time_tick--;

            alarm_sta++;

        }

        else

        {

            sht30[i].sht_errcode = NORMAL;

            sht30[i].time_tick--;

        }

        

    }

    if(alarm_sta > 0)

    {

        alarm_ON();

    }

    else

    {

        alarm_OFF();

    }

    rt_thread_mdelay(500);

}

}

/* 巡检任务 */

void sht30_check(void)

{

rt_thread_init(&tid_check_sht30,

                "sht30_check",

                thread_sht30_check_entry, 

                RT_NULL,

                &thread_sht30_check_stack[0],

                sizeof(thread_sht30_check_stack),

                THREAD_PRIORITY - 1, THREAD_TIMESLICE);

rt_thread_startup(&tid_check_sht30);

}

9.显示任务,为定时按照传感器的工作状态来实现数据的展示,主要是根据三个状态、以及温湿度是否超过或者低于限值来显示不同的颜色,代码如下:

/* 线程 显示 的入口函数 */

static void thread_lcd_entry(void *parameter)

{

sht30_data_Init();

char buff_temp[15];

char buff_humi[15];

uint16_t temp_background_color, temp_font_color;

uint16_t humi_background_color, humi_font_color;

int y_offset = 0;

int i = 0;

while (1)

{

    y_offset = 46;

    for(i=0;i< maxID;i++)

    {

        rt_kprintf("sensorID:%d  stata: %d", i+1, sht30[i].sht_errcode);

        y_offset = y_offset + i*70;

        sprintf(buff_temp,"%d%d.%d",sht30[i].temp/100, sht30[i].temp/10%10, sht30[i].temp%10);

        sprintf(buff_humi,"%d%d.%d",sht30[i].humi/100, sht30[i].humi/10%10, sht30[i].humi%10);  

        switch (sht30[i].sht_errcode)

        {

        case NORMAL:

            temp_background_color = GRAY0;

            temp_font_color = BLUE;

            humi_background_color = GRAY0;

            humi_font_color = BLUE;

            break;

        case OFFLINE: 

            temp_background_color = GRAY2;

            temp_font_color = BLUE;

            humi_background_color = GRAY2;

            humi_font_color = BLUE;

            sprintf(buff_temp, "    ");

            sprintf(buff_humi, "    ");

            break;      

        case ABNORMAL:

            if(sht30[i].humi< sht30[i].humi_lower_limit || sht30[i].humi > sht30[i].humi_upper_limit)

            {

                humi_background_color = YELLOW;

                humi_font_color = BLACK;

            }

            else

            {

                humi_background_color = GRAY0;

                humi_font_color = BLUE;

            }

            if(sht30[i].temp< sht30[i].temp_lower_limit || sht30[i].temp > sht30[i].temp_upper_limit)

            {

                temp_background_color = YELLOW;

                temp_font_color = BLACK;

            }

            else

            {

                temp_background_color = GRAY0;

                temp_font_color = BLUE;

            }

            break;       

        default:

            break;

        }



        Gui_DrawFont_GBK16(90,y_offset,temp_font_color,temp_background_color,buff_temp);        //更新显示

        Gui_DrawFont_GBK16(90,y_offset+20,humi_font_color,humi_background_color,buff_humi);



    }

      

    rt_thread_mdelay(10000);

}

}

/* 显示任务 */

void lcd_show(void)

{

rt_thread_init(&tid_show_sht30,

                "lcd show",

                thread_lcd_entry, 

                RT_NULL,

                &thread_lcd_show_stack[0],

                sizeof(thread_lcd_show_stack),

                THREAD_PRIORITY - 1, THREAD_TIMESLICE);

rt_thread_startup(&tid_show_sht30);

}

【工程效果】

RT-Thread

RT-Thread

1、无线数据采集端能实现的采集数据,并按照设定的时间实现超远距离、超低功耗的长时间运行,经测量功耗情况如下:

RT-Thread

从上面的数据我们可以看出,待机电流为7.5微安左右,在每两分钟启用一次数据上报,最在工作电流为46.5mA,平均电流为110uA,平均功率为362微瓦。可以推算一下,1000mAH的电池可以持续供电100天左右。如果我们采用在温湿度正常的范围内缓存,每一个小时做一次数据上传,那么预计可以延长30倍的工作时间,那就是10年左右的待机。

2、主机端,我们可以实时的监控无线数据采集工作站的实现状况。

RT-Thread

离线的警示:

RT-Thread

温度异常:

RT-Thread

【项目总结】

经过半个月的项目开发,主要实现了RT-Thread Nano移植,温湿度计、无线串口模块、LCD屏的驱动。实现了一套温湿度监测系统的基本功能。

【项目拓展计划】

下一步,将继续完善监控系统。

1、进行数据储存。

2、数据的历史数据查看。

3、连接互联网,把数据分发给服务器。

审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分