打印
[CW32L083系列]

超低功耗温湿度计

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

#有奖活动# #申请原创# @21小跑堂【开发环境】
1、硬件:CW32L083VxTx StartKit 开发板,板载有8位LCD段码屏。
2、软件环境:MDK5。
3、温湿度计:SHT30。
【硬件连接】
开发板        SHT30
PB11           SDA
PB10           SCL
DVCC          VCC
DVSS          GND
【功耗测试环境】
合宙IoT Power功耗测试神器。
【硬件框图】

【软件流程图】

【主要代码设计】
本工程主要代码功能为温湿传感器SHT30的数据采集、LCD显示、RTC自动唤醒。下面展示三个功能模块的主要代码:
1、SHT30采集模拟IIC通信,主要是IIC的时序产生,与SHT30的单次采集指令发送与数据读取以及CRC。
IIC的时序产生主要代码如下:
void IIC_Init(void)
{
          //配置PB10 为输出
        //使能GPIOB时钟
        CW_SYSCTRL->AHBEN_f.GPIOB  = 1;
        //配置PB10 为输出
        CW_GPIOB->ANALOG_f.PIN10 = 0; //设置 GPIOx_ANALOG.PINy 为 0,将端口配置为数字功能;
        CW_GPIOB->DIR_f.PIN10 = 0;    //设置 GPIOx_DIR.PINy 为 0,将端口配置成输出;
        CW_GPIOB->OPENDRAIN_f.PIN10 = 0;  //0:推挽输出
        CW_GPIOB->ODR_f.PIN10 = 1;
        
        CW_GPIOB->ANALOG_f.PIN11 = 0; //设置 GPIOx_ANALOG.PINy 为 0,将端口配置为数字功能;
        CW_GPIOB->DIR_f.PIN11 = 0;    //设置 GPIOx_DIR.PINy 为 0,将端口配置成输出;
        CW_GPIOB->OPENDRAIN_f.PIN11 = 0;  //0:推挽输出
        CW_GPIOB->ODR_f.PIN11 = 1;
               
}
                                                                                                            
//IO方向设置(SDA)
/*********xxxxxxxxxxxxxx*************/
void SDA_IN()  
{
        CW_GPIOB->DIR_f.PIN11 = 1;    //设置 GPIOx_DIR.PINy 为 0,将端口配置成输出;
}

void SDA_OUT()
{
  CW_GPIOB->DIR_f.PIN11 = 0;    //设置 GPIOx_DIR.PINy 为 0,将端口配置成输出;
        CW_GPIOB->OPENDRAIN_f.PIN11 = 0;  //0:推挽输出
}

//产生IIC起始信号
void IIC_Start(void)
{
        SDA_OUT();     //sda线输出
        IIC_SDA=1;                    
        IIC_SCL=1;
        delay_us(4);
         IIC_SDA=0;//START:when CLK is high,DATA change form high to low
        delay_us(4);
        IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}        

//产生IIC停止信号
void IIC_Stop(void)
{
        SDA_OUT();//sda线输出
        IIC_SCL=0;
        IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
         delay_us(4);
        IIC_SCL=1;
        IIC_SDA=1;//发送I2C总线结束信号
        delay_us(4);                                                                  
}

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
/*********xxxx修改超时时间************/
uint8_t IIC_Wait_Ack(void)
{
        uint8_t ucErrTime=0;
        SDA_IN();      //SDA设置为输入  
        IIC_SDA=1;delay_us(3);           
        IIC_SCL=1;delay_us(3);         
        while(READ_SDA)
        {
                ucErrTime++;
                if(ucErrTime>250)
                {
                        //printf("超时\n");
                        IIC_Stop();
                        return 1;
                }
        }
        IIC_SCL=0;//时钟输出0            
        return 0;  
}

//产生ACK应答
void IIC_Ack(void)
{
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=0;
        delay_us(2);
        IIC_SCL=1;
        delay_us(2);
        IIC_SCL=0;
}

//不产生ACK应答                    
void IIC_NAck(void)
{
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=1;
        delay_us(2);
        IIC_SCL=1;
        delay_us(2);
        IIC_SCL=0;
}                        


//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答                          
void IIC_Send_Byte(uint8_t txd)
{                        
    uint8_t t;   
                SDA_OUT();            
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
                if((txd&0x80)>>7)
                        IIC_SDA=1;
                else
                        IIC_SDA=0;
                txd<<=1;           
                delay_us(2);   //对TEA5767这三个延时都是必须的
                IIC_SCL=1;
                delay_us(2);
                IIC_SCL=0;        
                delay_us(2);
    }         
}           


//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
uint8_t IIC_Read_Byte(unsigned char ack)
{
        unsigned char i,receive=0;
        SDA_IN();//SDA设置为输入
  for(i=0;i<8;i++ )
        {
        IIC_SCL=0;
        delay_us(100);
                    IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
                    delay_us(100);
    }                                         
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}
SHT30的测量指令与数据获取及CRC主要代码如下:
#include "sht30.h"

#define POLYNOMIAL_CXDZ 0x31 // X^8 + X^5 + X^4 + 1
//SHT3X CRC校验
unsigned char SHT3X_CRC(uint8_t *data, uint8_t len)
{
        unsigned char bit;        // bit mask
        unsigned char crc = 0xFF; // calculated checksum
        unsigned char byteCtr;    // byte counter

        // calculates 8-Bit checksum with given polynomial @GZCXDZ
        for(byteCtr = 0; byteCtr < len; byteCtr++) {
                        crc ^= (data[byteCtr]);
                        for(bit = 8; bit > 0; --bit) {
                                        if(crc & 0x80) {
                                                        crc = (crc << 1) ^ POLYNOMIAL_CXDZ;
                                        }  else {
                                                        crc = (crc << 1);
                                        }
                        }
        }
  return crc;
}

//SHT30命令函数
//addr:表示产品的序号,因为SHT30使用IIC总线的话一条线上可以挂两个
void SHT30_CMD(uint16_t cmd)
{
        IIC_Start();
  IIC_Send_Byte(SHT30_ADDR+0);  //发送设备地址,写寄存器
        IIC_Wait_Ack();
        IIC_Send_Byte((cmd>>8)&0xff); //MSB

        IIC_Wait_Ack();
        IIC_Send_Byte(cmd&0xff); //LSB

        IIC_Wait_Ack();
        IIC_Stop();
        SysTickDelay(500);//命令发完后需要等待20ms以上才能读写
}



//SHT30读取温湿度
//temp:温度,-400~1250,实际温度=temp/10,分辨率0.1℃,精度±0.3℃
//humi:湿度,0~1000,实际湿度=humi/10,分辨率0.1%rh,精度±3
//返回0成功,1失败
uint8_t SHT30_Read_Humiture(int *temp,uint16_t *humi)
{
        uint8_t buff[6];
        
        SHT30_CMD(SHT30_READ_HUMITURE);//读温湿度命令
        
        IIC_Start();
        IIC_Send_Byte(SHT30_ADDR+1); //发送设备地址,读寄存器
        IIC_Wait_Ack();
        buff[0]=IIC_Read_Byte(1);//继续读,给应答
        buff[1]=IIC_Read_Byte(1);//继续读,给应答
        buff[2]=IIC_Read_Byte(1);//继续读,给应答
        buff[3]=IIC_Read_Byte(1);//继续读,给应答
        buff[4]=IIC_Read_Byte(1);//继续读,给应答
        buff[5]=IIC_Read_Byte(0);//不继续给停止应答
        IIC_Stop();

        
        //printf("buff=%d,%d,%d,%d,%d,%d\r\n",buff[0],buff[1],buff[2],buff[3],buff[4],buff[5]);
        //CRC校验
        if(SHT3X_CRC(&buff[0],2)==buff[2] && SHT3X_CRC(&buff[3],2)==buff[5])
        {

                *temp=(-45+(175.0*((buff[0]<<8)+buff[1])/65535.0))*10;
                *humi=10*100*((buff[3]<<8)+buff[4])/65535.0;
                if(*temp>1250) *temp=1250;
                else if(*temp<-400) *temp=-400;
                return 0;
        }
        else return 1;        
        
}

//SHT30初始化
void SHT30_Init()
{
        IIC_Init();
}
2、LCD屏的显示,分为两个部分,一个是定义了段码显示的高、低位显示数组; 二是封装了数量显示了函数,具体代码如下:
/*  段码低8(左) */
static uint8_t num_L[10] = {
        0x0d, //0
        0x00, //1
        0x0e, //2
        0x0a, //3
        0x03, //4
        0x0b,  //5
        0x0f,  //6
        0x00,  //7
        0x0f,  //8
        0x0b,  //9
        
};
/*  段码高8(右) */
static uint8_t num_H[10] = {
        0x07,
        0x06,
        0x03,
        0x07,//3
        0x06,//4
        0x05, //5
        0x05, //
        0x07, //7
        0x07, //8
        0x07, //9
};

void Lcd_clear(void)
{
        CW_LCD->RAM0 = 0;
  CW_LCD->RAM1 = 0;
  CW_LCD->RAM8 = 0;
  CW_LCD->RAM9 = 0;
}
void show_nums(uint32_t num)
{
        uint8_t i=0;
        uint8_t j;
        uint32_t temp;
        temp = num;
        //空显示
        Lcd_clear();
        if(temp == 0)
                show_num(0,0,0);
        while(temp>0)
        {
                j = temp%10;
                show_num(i,j,0);
                temp /=10;
                i++;
        }
}

/**
*功能:显示数字到LCD段码屏上
*输入参数1:显示在哪个位上7-0
*输入参数2:需要显示数字
*输入参数3:是否需要显示小数点
*/

void show_num(uint8_t wei, uint8_t num, uint8_t doit)
{
        uint8_t temp_H;
        temp_H = num_H[num];
        if(0 != doit)
        {
                temp_H  = temp_H + 8 ; //第四位置1显示小数点
        }

        switch(wei)
        {
                case 7:
                {
                                //显示第7个数码管
                        CW_LCD->RAM0 |= temp_H  <<8 | num_L[num];
                        break;
                }
                case 6:
                {
                        //显示第6个数码管
                        CW_LCD->RAM0 |= (temp_H<<8 | num_L[num]) <<16;
                        break;
                }
                case 5:
                {
                        //显示第5个数码管
                        CW_LCD->RAM1 |= num_L[num];
                        CW_LCD->RAM8 |= temp_H;
                        break;
                }
                case 4:
                {
                        //显示第4个数码管
                        CW_LCD->RAM8 |= temp_H<<16 | num_L[num]<<8;
                        break;
                }
                case 3:
                {
                        //显示第3个数码管
                        CW_LCD->RAM8 |= num_L[num]<<24;
      CW_LCD->RAM9 |= temp_H;
                        break;
                }
                case 2:
                {
                        //显示第2个数码管
                        CW_LCD->RAM9 |= temp_H<<16 | num_L[num]<<8;
                        break;
                }
                case 1:
                {
                        //显示第1个数码管
                        CW_LCD->RAM1 |= temp_H<<8;
                        CW_LCD->RAM9 |= num_L[num]<<24;
                        break;
                }
                case 0:
                {
                        //显示第0个数码管
                        CW_LCD->RAM1 |= temp_H<<24 | num_L[num]<<16;
                        break;
                }
        }
                                
}

void LCD_Configuration(void)
{
    LCD_InitTypeDef LCD_InitStruct = {0};

    LCD_InitStruct.LCD_Bias = LCD_Bias_1_3;
    LCD_InitStruct.LCD_ClockSource = LCD_CLOCK_SOURCE_LSI;
    LCD_InitStruct.LCD_Duty = LCD_Duty_1_4;
    LCD_InitStruct.LCD_ScanFreq = LCD_SCAN_FREQ_128HZ;
    LCD_InitStruct.LCD_VoltageSource = LCD_VoltageSource_Internal;

    __RCC_LCD_CLK_ENABLE();
    RCC_LSI_Enable();
    LCD_Init(&LCD_InitStruct);     //基本配置
    // BTL004 LCD 对应的连接
    //PA12 COM3
    //PA11 COM2
    //PA10 COM1
    //PA09 COM0
    //PA08 SEG0
    //PC09 SEG1
    //PC08 SEG2
    //PC07 SEG3
    //PC06 SEG4
    //PD15 SEG32
    //PD14 SEG33
    //PD13 SEG34
    //PD12 SEG35
    //PD11 SEG36
    //PD10 SEG37
    //PD09 SEG38
    //PD08 SEG39
    //PB15 SEG5
    //PB14 SEG6
    //PB13 SEG7
    // 分配引脚
    LCD_COMConfig(LCD_COM0 | LCD_COM1 | LCD_COM2 | LCD_COM3, ENABLE);
    LCD_SEG0to23Config(0x0000FF, ENABLE);
    LCD_SEG32to55Config(0x0000FF,ENABLE);

    CW_LCD->RAM[0] = 0;
    CW_LCD->RAM[1] = 0;
    CW_LCD->RAM2 = 0;
    CW_LCD->RAM3 = 0;
    CW_LCD->RAM4 = 0;
    CW_LCD->RAM5 = 0;
    CW_LCD->RAM6 = 0;
    CW_LCD->RAM7 = 0;
    CW_LCD->RAM8 = 0;
    CW_LCD->RAM9 = 0;
    CW_LCD->RAM10 = 0;
    CW_LCD->RAM11 = 0;
    CW_LCD->RAM12 = 0;
    CW_LCD->RAM13 = 0;

    LCD_Cmd(ENABLE);
    CW_LCD->RAM0 = 0;
    LCD_ContrastConfig(LCD_Contrast_Level_6);
    LCD_DriveVoltageConfig(LCD_INRS_LEVEL_0);
}
3、功耗控制主要是通过进入深度睡眠模式来实现节能,并通过RTC的AWT模块来实现定时唤醒。在此模块中,我们配置了AWT时钟源为RTC_AWTSOURCE_FROM_RTC1HZ_1即1秒为单位的唤醒,我们可以通过RTC_AWTARR 唤醒定时器重载值,来实现以秒为单位的休眠时长。主要代码如下:
//进入低功耗设置
void entry_power(void)
{
//        //1,先判断是否上电复位
    RTC_InitTypeDef RTC_InitStruct = {0};
          RTC_AWTTypeDef RCT_AWTStruct = {0};
          RCC_LSE_Enable(RCC_LSE_MODE_OSC, RCC_LSE_AMP_NORMAL, RCC_LSE_DRIVER_NORMAL);  // 选择LSE为RTC时钟

          RTC_InitStruct.DateStruct.Day = 0x24;             //设置日期,DAY、MONTH、YEAR必须为BCD方式,星期为0~6,代表星期日,星期一至星期六
                RTC_InitStruct.DateStruct.Month = RTC_Month_June;
                RTC_InitStruct.DateStruct.Week = RTC_Weekday_Monday;
                RTC_InitStruct.DateStruct.Year = 0x23;

                RTC_InitStruct.TimeStruct.Hour = 0x11;         //设置时间,HOUR、MINIUTE、SECOND必须为BCD方式,用户须保证HOUR、AMPM、H24之间的关联正确性
                RTC_InitStruct.TimeStruct.Minute = 0x58;
                RTC_InitStruct.TimeStruct.Second = 0x59;
                RTC_InitStruct.TimeStruct.AMPM = 0;
                RTC_InitStruct.TimeStruct.H24 = 0;
                RTC_InitStruct.RTC_ClockSource = RTC_RTCCLK_FROM_LSE;
                RTC_Init(&RTC_InitStruct);    //
        //设置自动唤醒
         RCT_AWTStruct.AWT_ClockSource = RTC_AWTSOURCE_FROM_RTC1HZ_1;
         RCT_AWTStruct.AWT_ARRValue = 60;
         RTC_AWTConfig(&RCT_AWTStruct);
         RTC_AWTCmd(ENABLE);
         RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_RTC, ENABLE);
         RTC_ITConfig(RTC_IT_AWTIMER, ENABLE);
}        
4、在主程序中,我们先初始基本外设后进行循环的采集——显示——休眠——唤醒来实现温湿度采集的目标,主程序主要代码如下:
int32_t main(void)
{
          uint16_t temp;
                int t[20];
                uint16_t h[20];
                RCC_Configuration();
                NVIC_Configuration();
                LCD_Configuration();
                InitTick(8000000);
                SHT30_Init();
                Lcd_clear();               
                SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
                entry_power();

    while(1)
    {
                        SHT30_Read_Humiture(t,h);
                        temp = t[0];
                        Lcd_clear();
                        show_num(2, temp/100,0);
                        show_num(1, (temp/10)%10,1);
                        show_num(0, temp%10,0);                        
                        temp = h[0];
                        show_num(7, temp/100,0);
                        show_num(6, (temp/10)%10,1);
                        show_num(5, temp%10,0);
                        CW_SYSCTRL->AHBEN_f.GPIOB  = 0;
                        __DSB();
                        __WFI();
                        SHT30_Init();
    }

}
【实现的效果】
我们设定60秒中唤醒进行一次温显度采集,实现了休眠电流为5uA,综合平均工作电流为13uA、平均功率为。基本满足了以电池供电的环境下的超长工作。


【讨论】
CW32L083集成了LCD控制器,可以实现数据采集、显示的超低功耗工作。非常适合用于电池供电的环境下工作。本次试验虽然获得了理想效果,但是还有一些可以改进的地方。
1、在待机中的主要电流产生是LCD屏产生的功耗,如果在特殊的环境下,不需要长时间显示,可以适时关闭LCD屏,这样可以节约差不多4uA的工作电流。启用按键来人工参与显示数据,这样又可以更进一步降低超机功耗。
2、在工作电流中,主要消耗的是SHT30的温度转换时产生的大电流。如果应用的生产环境,可以在等待温度转换时,降低MCU的主频或者进入sleep模式以降低能耗。


使用特权

评论回复
评论
forgot 2024-1-22 13:58 回复TA
有实物 
lin624719 2023-7-15 18:33 回复TA
有实物的楼主就是牛 
cooldog123pp 2023-7-12 12:44 回复TA
有实物就牛X! 
沙发
dami| | 2023-6-25 09:41 | 只看该作者
楼主牛B!

使用特权

评论回复
板凳
lulugl|  楼主 | 2023-6-25 11:32 | 只看该作者

大佬过奖了

使用特权

评论回复
地板
wzjhuohua| | 2023-6-25 14:27 | 只看该作者
我的目标是60uA应该很容易达到吧

使用特权

评论回复
5
lulugl|  楼主 | 2023-6-25 14:28 | 只看该作者
wzjhuohua 发表于 2023-6-25 14:27
我的目标是60uA应该很容易达到吧

很容易的。加油!

使用特权

评论回复
6
coody| | 2023-6-25 20:03 | 只看该作者
我用STC8H4K64TLCD LQFP64组的时钟+温度+湿度+北斗模块或电波钟模块校时(一天一次),非常省电,8个8段LCD,十几uA。

使用特权

评论回复
评论
forgot 2024-1-22 13:58 回复TA
赞 
7
lulugl|  楼主 | 2023-6-25 20:23 | 只看该作者
coody 发表于 2023-6-25 20:03
我用STC8H4K64TLCD LQFP64组的时钟+温度+湿度+北斗模块或电波钟模块校时(一天一次),非常省电,8个8段LCD ...

大佬技术非常牛,我还是一个小菜鸟,以后请大佬多多关照。

使用特权

评论回复
8
renzhen123| | 2023-7-3 00:02 | 只看该作者
学习了

使用特权

评论回复
9
lulugl|  楼主 | 2023-7-12 15:14 | 只看该作者

多谢大佬关注!

使用特权

评论回复
10
yuzhiguo1515| | 2023-7-14 11:44 | 只看该作者
谢谢分享,学习学习。

使用特权

评论回复
11
地瓜patch| | 2023-7-15 21:37 | 只看该作者
我做过低功耗温湿度计,休眠在nA

使用特权

评论回复
12
lulugl|  楼主 | 2023-7-16 08:34 | 只看该作者
地瓜patch 发表于 2023-7-15 21:37
我做过低功耗温湿度计,休眠在nA

优秀的芯片跟设计者非常多,还请大佬多指点一下。**能分享一下大栳经验,让我们这些晚辈学习一下。

使用特权

评论回复
13
AdaMaYun| | 2023-11-21 16:27 | 只看该作者
不愧是我一直努力往后翻找,楼主厉害,学习

使用特权

评论回复
14
星辰大海不退缩| | 2024-1-21 15:35 | 只看该作者
CW32L083集成了LCD控制器,可以实现数据采集、显示的超低功耗工作

使用特权

评论回复
15
jf101| | 2024-1-27 14:22 | 只看该作者
综合平均工作电流为13uA这个功率非常低

使用特权

评论回复
16
AdaMaYun| | 2024-1-27 16:08 | 只看该作者
这个屏幕应该也消耗不大

使用特权

评论回复
17
OKAKAKO| | 2024-1-27 16:29 | 只看该作者
8位LCD段码屏功耗应该不高

使用特权

评论回复
18
LOVEEVER| | 2024-1-27 17:54 | 只看该作者
功耗控制主要是通过进入深度睡眠模式来实现节能

使用特权

评论回复
19
小夏天的大西瓜| | 2024-1-27 20:54 | 只看该作者
重点的低功耗的功率计算如何统计的?

使用特权

评论回复
20
szt1993| | 2024-2-25 16:34 | 只看该作者
硬件:CW32L083VxTx StartKit 开发板,板载有8位LCD段码屏。

使用特权

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

本版积分规则

145

主题

715

帖子

9

粉丝