打印
[CW32L083系列]

CW32L083智能温湿度监控系统

[复制链接]
1021|19
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lulugl|  楼主 | 2023-7-7 07:51 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 lulugl 于 2023-7-7 09:08 编辑

#申请原创# #有奖活动#@21小跑堂
【系统功能】
CW32l083为主控制的无线终端数据收发。运行国产RT-Thread操作系统。主要功能为实现用E31-TTL-50接收各个模块发送上来的数据,解析数据,分析数据,显示数据,并实现信息的显示,以及异常情况的显示、警告功能。
无线终端主要以cw32L031为主控,采集sht30温湿度传感器数据,通过E31-TTL-50无线模块将数据上传。实现5微安的待机电流的超低功耗。
【功能模块】
主机:
  • 接收模块:接收无线终端模块发送的温湿度数据,解析数据后,更新模块数据。
  • 巡检模块:定时巡检各个无线终端的数据,判定工作状况、更新显示、报警标志。
  • 显示模块:根据各个模块的工作状态,生成为示数据,用TFT屏展示。
报警模块:驱动pwm模块,装载pwm重载值,发出警示声音。

无线终端:
  • 温湿采集模块:采集sht30数据。
  • 发送模块:将数据打包,通过无线发送。
  • 休眠模块:发送完数据后进入深度休眠状态,由AWT模块定时唤醒。
【硬件】
主机:
  • CW32L083VxTx StartKit REV01开发板。
  • ST7735TFT显示屏。
  • E31-TTL-50无线串口模块。
无线终端:
  • cw32l031开发板
  • Sht30温湿度传感器。
  • E31-TTL-50无线串口模块。
  • 可充电锂电池。
【开发环境】
  • 代码编译环境采集ubuntu20.4;
  • 代码编辑工具为vscode 1.79.2;
  • 交叉编译器为arm-nano-eabi-gcc;
  • 固件库为cw32提供的固件库;
  • gcc启动文件与链接由作者在cortex-M0+的其他软件上移植过来;
  • 下载器为CW32配送的wch-link;
  • 代码下载软件为pyocd;
  • 调式工具为gdb。
本次开发板的编译环境、工具均采用开源工具。
【操作系统】
本工程的主控,作者移植了RTT-Thread Nano 3.15版本。RTT作为一款国产开源免费的操作系统可以提供强大的功能,为CW32的性能发挥提供强力的支持。
【程序流程图】
  • 主机端由RTT开启两个主要任务,用于数据显示与巡检,同时利用串口中断来实时处理接收的数据。GTIM定时开启PWM任务,来驱动开发板板载的BEEP。流程图如下:

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

【原理图】
  • 无线端终采集:

  • 主机端:

【程序设计】
无线采集端
IIC初始化,采用模拟I2C主要代码是对
主机端
  • 主机端我们处理数据的核心为sht30数据,声明结构体如下:
typedefstruct_sht30_data
{
    uint32_tID;
    inttemp; //温度值
    inttemp_upper_limit; //温度值上限
    inttemp_lower_limit; //温度值下限
    uint16_thumi; //湿度
    uint16_thumi_upper_limit; //湿度上限
    uint16_thumi_lower_limit; //湿度上限
    uint32_t  time_tick;      //更新数据计时
    enum_sht30_errcodesht_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:
voidRCC_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,初始化代码为:
voidE31_UART_Init(void)
{
    uint32_tPCLK_Freq;
    GPIO_InitTypeDefGPIO_InitStructure= {0};
    UART_InitTypeDefUART_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);
}
6、同时配置中断函数,主要功能是判断是否接到了帧属,如果接收到帧尾则把数据交给回调函数进行处理,代码如下:
voidUART1_UART4_IRQHandler(void)
{
    /* USER CODE BEGIN */
    uint8_tTxRxBuffer;
    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();
            }
            elseif ((TxRxBuffer==0x0D) && (e31_rx_state==0))
            {
                e31_rx_state=1;
            }
            elseif (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 */
}
7、同时回调函数,为处理与解析数据更新到sht30数据之中:
voide31_exp_data(void)
{
    inttemp;
    uint16_thumi;
    uint32_tID;
    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;
}
8、ST7735的驱动,驱动采集模拟SPI进行驱动,详细的驱动见工程源码包。
9、PWM驱动,pwm选用PA6为pwm输出端,初始化为1KHz的输出来驱动板载的蜂鸣器。在初始化驱动后,我们装载最大的装截时,占空比为100%,使得蜂鸣器停止,在后面的需要输入报警声后,调整为50%的占空比,来实现蜂鸣器的报警声:
voidinit_beep(void)
{
    GTIM_InitTypeDefGTIM_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);
}
voidalarm_ON(void)
{
    GTIM_SetCompare1(CW_GTIM1, 16000-1);;
}
voidalarm_OFF(void)
{
    GTIM_SetCompare1(CW_GTIM1, 32000-1);
}
10、按照程序流程图,我们创建了两个任务,一个为巡检任务来实现对传感器模块的数据监控,并实理更新工作状态,代码如下:
/* 巡检任务 */
voidthread_sht30_check_entry(void*parameter)
{
    inti;
    uint8_talarm_sta;
    while(1)
    {   alarm_sta=0;
        for(i=0; i<maxID; i++)
        {
            if(sht30.time_tick==0)
            {
                //发送离线的警告
                sht30.sht_errcode=OFFLINE;
                sht30.temp=0;
                sht30.humi=0;
                alarm_sta++;
            }
            elseif (sht30.temp<sht30.temp_lower_limit\
                     ||sht30.temp>sht30.temp_upper_limit\
                     ||sht30.humi<sht30.humi_lower_limit\
                     ||sht30.humi>sht30.humi_upper_limit )
            {
                sht30.sht_errcode=ABNORMAL;
                sht30.time_tick--;
                alarm_sta++;
            }
            else
            {
                sht30.sht_errcode=NORMAL;
                sht30.time_tick--;
            }

        }
        if(alarm_sta>0)
        {
            alarm_ON();
        }
        else
        {
            alarm_OFF();
        }
        rt_thread_mdelay(500);
    }
}
/* 巡检任务 */
voidsht30_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);
}
12、显示任务,为定时按照传感器的工作状态来实现数据的展示,主要是根据三个状态、以及温湿度是否超过或者低于限值来显示不同的颜色,代码如下:
/* 线程 显示 的入口函数 */
staticvoidthread_lcd_entry(void*parameter)
{
    sht30_data_Init();
    charbuff_temp[15];
    charbuff_humi[15];
    uint16_ttemp_background_color, temp_font_color;
    uint16_thumi_background_color, humi_font_color;
    inty_offset=0;
    inti=0;
    while (1)
    {
        y_offset=46;
        for(i=0;i<maxID;i++)
        {
            rt_kprintf("sensorID:%d  stata: %d", i+1, sht30.sht_errcode);
            y_offset=y_offset+i*70;
            sprintf(buff_temp,"%d%d.%d",sht30.temp/100, sht30.temp/10%10, sht30.temp%10);
            sprintf(buff_humi,"%d%d.%d",sht30.humi/100, sht30.humi/10%10, sht30.humi%10);  
            switch (sht30.sht_errcode)
            {
            caseNORMAL:
                temp_background_color=GRAY0;
                temp_font_color=BLUE;
                humi_background_color=GRAY0;
                humi_font_color=BLUE;
                break;
            caseOFFLINE:
                temp_background_color=GRAY2;
                temp_font_color=BLUE;
                humi_background_color=GRAY2;
                humi_font_color=BLUE;
                sprintf(buff_temp, "    ");
                sprintf(buff_humi, "    ");
                break;      
            caseABNORMAL:
                if(sht30.humi<sht30.humi_lower_limit||sht30.humi>sht30.humi_upper_limit)
                {
                    humi_background_color=YELLOW;
                    humi_font_color=BLACK;
                }
                else
                {
                    humi_background_color=GRAY0;
                    humi_font_color=BLUE;
                }
                if(sht30.temp<sht30.temp_lower_limit||sht30.temp>sht30.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);
    }

}
/* 显示任务 */
voidlcd_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);
}
【工程效果】
采集终端搭建型1:

采集终端搭建型2:

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

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

离线的警示:

温度异常:

【项目总结】
经过半个月的项目开发,主要实现了RT-Thread Nano移植,温湿度传感器、无线串口模块、LCD屏的驱动。实现了一套温湿度监测系统的基本功能。
【项目拓展计划】
下一步,将继续完善监控系统。
  • 进行数据储存。
  • 数据的历史数据查看。
  • 连接互联网,把数据分发给服务器。
【视频展示】




使用特权

评论回复
沙发
forgot| | 2023-7-10 10:56 | 只看该作者
赞,国产RT-Thread操作系统还没跑过,楼主方案做的很好,路过学习一下

使用特权

评论回复
板凳
lulugl|  楼主 | 2023-7-10 16:00 | 只看该作者
forgot 发表于 2023-7-10 10:56
赞,国产RT-Thread操作系统还没跑过,楼主方案做的很好,路过学习一下

感谢大佬的关注。

使用特权

评论回复
地板
szt1993| | 2023-7-11 21:38 | 只看该作者
楼主方案挺好的方便,能够高速运行相关的各个软件模块,模块化设计更方便

使用特权

评论回复
5
lulugl|  楼主 | 2023-7-11 22:44 | 只看该作者
szt1993 发表于 2023-7-11 21:38
楼主方案挺好的方便,能够高速运行相关的各个软件模块,模块化设计更方便 ...

多谢大佬的肯定,程序还在继续搭建。后面要实现串口的数据配置,对接互联网等。

使用特权

评论回复
6
tpgf| | 2023-8-7 09:33 | 只看该作者
这个睡眠时间根据需要进行设置的吧 可以灵活改变吗

使用特权

评论回复
7
lulugl|  楼主 | 2023-8-7 09:41 | 只看该作者
tpgf 发表于 2023-8-7 09:33
这个睡眠时间根据需要进行设置的吧 可以灵活改变吗

可以设置,但是这在这次设计里面没有设置。可以添加按键或者出厂时调整。

使用特权

评论回复
8
guijial511| | 2023-8-7 09:54 | 只看该作者
这是一个串口无线模块?

使用特权

评论回复
9
lulugl|  楼主 | 2023-8-7 09:57 | 只看该作者
guijial511 发表于 2023-8-7 09:54
这是一个串口无线模块?

对的,以前用过的无线串口模块。

使用特权

评论回复
10
qcliu| | 2023-8-7 10:15 | 只看该作者
这个是只用来显示温度和湿度 而没有通过温湿度来控制什么是吗

使用特权

评论回复
11
lulugl|  楼主 | 2023-8-7 10:26 | 只看该作者
qcliu 发表于 2023-8-7 10:15
这个是只用来显示温度和湿度 而没有通过温湿度来控制什么是吗

有个pwm控制蜂鸣器的,来提醒有温度异常。

使用特权

评论回复
12
lulugl|  楼主 | 2023-8-7 10:28 | 只看该作者
qcliu 发表于 2023-8-7 10:15
这个是只用来显示温度和湿度 而没有通过温湿度来控制什么是吗

目前还是主动监测为主,因为冷链设备已经有自己的控制系统。如果有其他的用途,可以增加控制的任务即可。因为L083是用rtt多任务操作系统来的。

使用特权

评论回复
13
drer| | 2023-8-7 11:41 | 只看该作者
温湿度测量系统其实并不涉及到反馈环是吧

使用特权

评论回复
14
lulugl|  楼主 | 2023-8-7 11:52 | 只看该作者
drer 发表于 2023-8-7 11:41
温湿度测量系统其实并不涉及到反馈环是吧

看实际的应用场景吧,目前我们用的产品,以数据收集为主,有一个项目有高低温后强制断电的功能,但是是很极端的场景下面。一般数值超过阈值,就会有人工干预的。

使用特权

评论回复
15
wiba| | 2023-8-7 12:14 | 只看该作者
如何发现长期接收不到数据从而进行弥补呢

使用特权

评论回复
16
lulugl|  楼主 | 2023-8-7 13:41 | 只看该作者
wiba 发表于 2023-8-7 12:14
如何发现长期接收不到数据从而进行弥补呢

接收端如果长期接收不到数据,就会发出离线警告。要保证发送的质量需要提供良好的天线。我的设备长期工作,目前还没有出现离线的情况。当然在发送数据时,最好采集一下电池的供电电压。这样方便提醒定期更换电池。当然在生产环境,肯定要增加许多的功能,比如增加看门狗等等,当然这样会增加功耗。在电池供电理想的情况,我们会增加数据的采集频率。

使用特权

评论回复
17
kxsi| | 2023-8-7 20:59 | 只看该作者
这种通过无线传输的 需要有握手的数据吗

使用特权

评论回复
18
coshi| | 2023-8-7 21:32 | 只看该作者
drer 发表于 2023-8-7 11:41
温湿度测量系统其实并不涉及到反馈环是吧

这个可不一定 得具体看是什么用途了

使用特权

评论回复
19
gangong| | 2024-10-24 21:27 | 只看该作者
非常棒,谢谢分享

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

156

主题

750

帖子

10

粉丝