• 正文
  • 相关推荐
申请入驻 产业图谱

CW32L031实现超远距离超低耗无线采集开发分享

21小时前
188
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

硬件环境

    CW32L031C8开发板SHT30温湿度传感器E31-TTL-50无线串口模块

开发板环境

Ubuntu20.0.4

代码编辑器

VSCODE ssh远程

编译器

arm-none-eabi-gcc

工程包

CW32L031_gcc工程包

工程概述

本工程的核心分为sht30数据采集后,经无线串口模块发送给上位机,利用自动唤醒模块休眠指定时长后再次唤醒系统进行数据采集。

初略原理图

程序流程图

主要代码

    自动唤醒定时器(AWT) 包含一个 16bit 向下计数器,并由一个可编程预分频器驱动。AWT 可选 5 种计数时钟源,可工作于定时模式或计数模式。当计数器时钟源为 LSE 或 LSI 时,AWT 可在深度休眠模式下保持运行,下溢出中断可唤醒 MCU 回到运行模式。具体配置代码如下:
void Init_awt_power(void){    AWT_TimeCntInitTypeDef AWT_TimeCntInitStruct = {0};    RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_AWT, ENABLE);  //Open AWT Clk    RCC_SystemCoreClockUpdate( RCC_Sysctrl_GetHClkFreq() );    RCC_LSI_Enable();

    AWT_TimeCntStructInit( &AWT_TimeCntInitStruct );    AWT_TimeCntInitStruct.AWT_ClkSource = AWT_CLKSOURCE_LSI;    AWT_TimeCntInitStruct.AWT_Prescaler = AWT_PRS_DIV32768;    AWT_TimeCntInitStruct.AWT_Mode = AWT_MODE_TIMECNT;    AWT_TimeCntInitStruct.AWT_Period = 120;    AWT_TimeCntInit(&AWT_TimeCntInitStruct);
    __disable_irq();    NVIC_EnableIRQ(AWT_IRQn);    __enable_irq();    //使能AWT下溢出中断    AWT_ITConfig(AWT_IT_UD, ENABLE);    AWT_Cmd(ENABLE);
    //DeepSleep唤醒时,保持原系统时钟来源    RCC_WAKEUPCLK_Config(RCC_SYSCTRL_WAKEUPCLKDIS);

}
    软件IIC的配置,这里使用软件模拟实现。具体代码如下:
#include "myiic.h"


#define  I2C1_SCL_GPIO_PORT       CW_GPIOB
#define  I2C1_SCL_GPIO_PIN        GPIO_PIN_10   
#define  I2C1_SDA_GPIO_PORT       CW_GPIOB
#define  I2C1_SDA_GPIO_PIN        GPIO_PIN_11  


void delay_us(uint32_t us)
{
    while(us--)
    {
        __NOP();
        __NOP();
        __NOP();
        __NOP();
        __NOP();
    }


}


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的采集程序如下:
#include "sht30.h"
#include "myiic.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(50);//命令发完后需要等待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();
}
    主程序中,我们首先对串口、IIC、AWT、SHT30进行初始化,然后进入采集程序,实现的代码如下:
int main(void)
{
    int t[6];
    uint16_t h[6];
    E31_UART_Init();
    SHT30_Init();
    USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);
    Init_awt_power();
    InitTick(24000000ul); //初始化SysTick
    // 开启两线调试接口
    RCC_SWDIO_Config(RCC_SYSCTRL_SWDIOEN);
    while (1)
    {
        SHT30_Read_Humiture(t,h);
        e31_send(t[0],h[0]);
        enter_lowpower();
        exit_lowpower();
    }


    return 0;
}

程序效果

模块采集的数据,在上位机的串口助手上接收到以16进制数据发送的温湿度数据。

上位机根据具体的需要再进行解析、判断或者分发。

功耗测试

此工程以合宙的IoT Power来采集功率耗数据,并做出基本的分析,具体效果如下图:

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

讨论

CW32L031具有超低功耗的出色性能,此实验的意义验证了在电池供电的环境下,可以持续的工作数年的可能。433M无线超远距离无线转输模块可以提供长达5公里(空旷)数据传输,广泛适用于智慧农业等野外的数据持续采集。也可以把温湿度传感器更改为土壤湿度、门禁等传感器,实现无线报警等功能。

扫码加入QQ群3群| 610403240

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

以开放、共享、互助为理念,致力于构建武汉芯源半导体CW32系列MCU生态社区。无论是嵌入式MCU小自还是想要攻破技术难题的工程师,亦或是需求解决方案的产品经理都可在CW32生态社区汲取营养、共同成长。