好久没更新博客了,甚至忘了Markdown的写作规则(该死),不过本人奉行能查尽量少记的原则,最近其实做了不少项目,今天分享一个我觉得有意思的项目。(巩固一下)

1.项目介绍

设备硬件分为两部分

第一部分为数据采集和数据处理,一共有三种传感器:温度、湿度、压力;由STM32L051c8t6进行运算处理;
第二部分为数据显示与上传,显示有两种:OLED、9位八段阴极数码管;数据无线传为Lora模块+PC上位机(显示数据与图形界面);

2.硬件选择

a.cpu选择stm32L0系列的低功耗和可靠性可是远近闻名,能有幸使用旗下的c8t6更是经典中的经典。

CPU内核 ARM Cortex-M0
CPU最大主频 32MHz
工作电压范围 1.65V~3.6V
内部振荡器 有
程序 FLASH容量 64K@x8bit
RAM总容量 8KB
EEPROM/数据 FLASH容量 2KB
GPIO端口数量 37
ADC(单元数/通道数/位数) 1@x10ch/12bit
外设/功能/协议栈 DMA;PWM;看门狗
工作温度范围 -40℃~+85℃
stm32L051c8t6数据手册.png

b.温湿度传感器

这两年疫情影响电子行业,加上美国对中国的元器件制裁,进口被严格限制,国外元器件价格涨了5倍不止,国产型号既便宜又可靠,能支持定制,关键是量多管饱很适合后期批量生产。
最后在一众国产温湿度传感器里选择了GXHT30这款(中科银河芯)
GXHT30.png
特征:
★ 全温湿度范围校准和温度补偿数字输出
★ 宽电源电压范围,从 2.2 V 到 5.5 V
★ I2C 接口,通信速度高达 1MHz
★ 两个用户可选择的地址
★ GXHT30 典型精度为±3%RH 和±0.3°C
★ GXHT31 典型精度为±2%RH 和±0.3°C
★ GXHT35 典型精度为±1.8%RH 和±0.1°C
★ 单芯片集成温湿传感器
★ 高可靠性和长期稳定性
★ 测量 0-100%范围相对湿度
★ 测量-45-130℃范围内温度
★ 集成 16 位高精度 ADC
★ 测量时间低至 2.5ms

c.气压传感器

压力传感器同样选择国产型号XGZP6847A
微信图片_20230106151452.png

d.无线传输模块

说到无线模块就不得不想到工业中最常用到的几个模块了:

模块lorazigbeewifi/蓝牙
参考网址网址网址
通讯距离<2500m<1000m<160m
频段398-525MHZ2.4GHZ802.11b/g/n
价格15元73元40元

很显然lora模块全方位胜出,为了方便测试,最终选择了正点原子的ATK-01Lora模块

至此最基本的元器件已经选择完成,剩余的元件可以算在电路的设计中,包块电源模块、触摸开关。。。

电路设计

不解释,(懒)直接放电路,数据采集端由最小系统和传感器构成:

采集端:

采集端.png
采集端PCB.png


处理端(无线):

处理端.png
处理端1.png
处理端2.png

程序设计(喜闻乐见)

基础部分由CUBMX辅助构建,暂不公开(还是懒),有兴趣的可以通过程序反推CUBEMX配置.
献丑了( •̀ ω •́ )✧

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
#include "SHT30.h"
#include "TM1640.h"
#include "Stdio.h"
#include "delay.h"
#include "String.h"
#include "stdio.h"
uint8_t aTxBuffer[]="\r\n*********data*********\r\n";
uint8_t aRxBuffer[1];        // 用来接收串口1发送的数据

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t I2CRXBuffer[6];
/*************************数码管数字**************************/
unsigned char const Num[12]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x63,0x39};//0 1 2 3 4 5 6 7 8 9,°,C

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/**********关闭GPIO时钟,降低功耗**********/
void Sys_Enter_Standby(void){
    __HAL_RCC_PWR_CLK_ENABLE();                                    //使能PWR时钟
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                    //清除Wake_UP标志
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN2);        //设置WAKEUP用于唤醒
    HAL_PWR_EnterSTANDBYMode();                                    //进入待机模式
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    //SHT30初始定义(iic)
    uint8_t Error = 0;
    float Temperature = 0.0,Humidity =0.0;
    //adc
     uint16_t ADC_temp1=0.0;
   float ADC_temp2=0.0;
    
    
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
    memset(I2CRXBuffer,0,sizeof(I2CRXBuffer));//接收数组初始化(iic)
    delay_init(32);
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();
    //TM1640_GPIO_INIT();
  /* USER CODE BEGIN 2 */
OLED_Init();
OLED_Clear();

HAL_ADCEx_Calibration_Start(&hadc,0xffffffff);//校准adc
int i=1;

unsigned int K;//压力
int K_g,K_s,K_n=0;

unsigned int T;//温度
int T_g,T_s,T_n=0;//个十百千位

unsigned int H;//湿度
int H_g,H_s,H_n=0;//个十百千位
 cls_TM1640();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
     while (i<=60)
  {
    HAL_UART_Transmit_IT(&huart1 ,aTxBuffer,sizeof(aTxBuffer)); //sizeof()可读取目标数组长度
    HAL_UART_Receive_IT(&huart1,aRxBuffer,1);
        //sht30
    if(SHT30_ValGet(I2CRXBuffer) == HAL_OK)
        {
            Error = SHT30_Dat_To_Float(I2CRXBuffer,&Temperature,&Humidity);
            if(!Error)
            {
        T=Temperature;
        T_g=T/1%10;//个位
        T_s=T/10%10;//十位
        H=Humidity;
        H_g=H/1%10;//个位
        H_s=H/10%10;//十位    
                
        printf("TEMP= %.2f,°C\r\n", Temperature);
        printf("RH= %.2f,RH\r\n", Humidity);
        printf("KPa= %.2f,KPa\r\n", ADC_temp2);
                
        OLED_ShowString (1,0,"TEMP",4);
      OLED_ShowString (1,3,"RH",2);
        OLED_ShowNum(40,0,Temperature,5,16);
      OLED_ShowNum(40,3,Humidity,5,16);
    disp(0xc1,Num[T_s]);
    disp(0xC2,Num[T_g]);    
    disp(0xc4,Num[H_s]);
    disp (0xc5,Num[H_g]);
                
            }
            else
            {
                printf("CRC Error\r\n");
            }
        }
        else
        {
            printf("SHT30_ValGet Error\r\n");
        }        
        
    //ADC
   HAL_ADC_Start(&hadc);//开始转换
   HAL_ADC_PollForConversion(&hadc,10);//等待转换结束
   if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc),HAL_ADC_STATE_REG_EOC))//检查是否转换结束
   {
   ADC_temp1=HAL_ADC_GetValue(&hadc);//八位就是0-256
   ADC_temp2=(float)ADC_temp1*2.5/256-29.6;//2.51是基准电压,这一步是吧模拟量转换成数字量
     K=ADC_temp2;
     K_s=T/10%10;//十位
     K_g=T/1%10;//个位
   printf("%d\r\n",i);
     OLED_ShowString (1,6,"KPA",3);
     OLED_ShowNum(40,6,ADC_temp2,5,16);
    disp (0xc7,Num[K_g]);
         
     } 

i++;
    HAL_Delay (800);
       
   }
    /* USER CODE END WHILE */
     /* USER CODE BEGIN 3 */
     OLED_Display_Off();
     Sys_Enter_Standby();
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLLMUL_8;
  RCC_OscInitStruct.PLL.PLLDIV = RCC_PLLDIV_2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_I2C1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

SHT30.C

#include "SHT30.h"


/**
 * @brief    向SHT30发送一条指令(16bit)
 * @param    cmd —— SHT30指令(在SHT30_MODE中枚举定义)
 * @retval    成功返回HAL_OK
*/
static uint8_t    SHT30_Send_Cmd(SHT30_CMD cmd)
{
    uint8_t cmd_buffer[2];
    cmd_buffer[0] = cmd >> 8;
    cmd_buffer[1] = cmd;
    return HAL_I2C_Master_Transmit(&hi2c1, SHT30_ADDR_WRITE, (uint8_t*) cmd_buffer, 2, 0xFFFF);
}

/**
 * @brief    单次测量数据获取
 * @param    dat —— 存储读取数据的地址(6个字节数组)
 * @retval    成功 —— 返回HAL_OK
*/
uint8_t SHT30_ValGet(uint8_t* dat)
{
    uint8_t Error;
    Error = SHT30_Send_Cmd(HIGH_ENABLED_CMD);
    HAL_Delay(50);
    if(Error != HAL_OK)
    {
        return Error;
    }
    
    return HAL_I2C_Master_Receive(&hi2c1, SHT30_ADDR_READ, dat, 6, 0xFFFF);
    
}

/**
 * @brief    复位SHT30
 * @param    none
 * @retval    none
*/
void SHT30_reset(void)
{
    SHT30_Send_Cmd(SOFT_RESET_CMD);
    HAL_Delay(20);
}

/**
 * @brief    初始化SHT30
 * @param    none
 * @retval    成功返回HAL_OK
 * @note    周期测量模式
*/
uint8_t SHT30_Init(void)
{
    return SHT30_Send_Cmd(MEDIUM_2_CMD);
}

/**
 * @brief    从SHT30读取一次数据
 * @param    dat —— 存储读取数据的地址(6个字节数组)
 * @retval    成功 —— 返回HAL_OK
 * @note    周期测量模式
*/
uint8_t SHT30_Read_Dat(uint8_t* dat)
{
    SHT30_Send_Cmd(READOUT_FOR_PERIODIC_MODE);
    return HAL_I2C_Master_Receive(&hi2c1, SHT30_ADDR_READ, dat, 6, 0xFFFF);
}

#define CRC8_POLYNOMIAL 0x31

uint8_t CheckCrc8(uint8_t* const message, uint8_t initial_value)
{
    uint8_t  remainder;        //余数
    uint8_t  i = 0, j = 0;  //循环变量

    /* 初始化 */
    remainder = initial_value;

    for(j = 0; j < 2;j++)
    {
        remainder ^= message[j];

        /* 从最高位开始依次计算  */
        for (i = 0; i < 8; i++)
        {
            if (remainder & 0x80)
            {
                remainder = (remainder << 1)^CRC8_POLYNOMIAL;
            }
            else
            {
                remainder = (remainder << 1);
            }
        }
    }

    /* 返回计算的CRC码 */
    return remainder;
}


/**
 * @brief    将SHT30接收的6个字节数据进行CRC校验,并转换为温度值和湿度值
 * @param    dat  —— 存储接收数据的地址(6个字节数组)
 * @retval    校验成功  —— 返回0
 *             校验失败  —— 返回1,并设置温度值和湿度值为0
*/
uint8_t SHT30_Dat_To_Float(uint8_t* const dat, float* temperature, float* humidity)
{
    uint16_t recv_temperature = 0;
    uint16_t recv_humidity = 0;
    
    /* 校验温度数据和湿度数据是否接收正确 */
    if(CheckCrc8(dat, 0xFF) != dat[2] || CheckCrc8(&dat[3], 0xFF) != dat[5])
        return 1;
    
    /* 转换温度数据 */
    recv_temperature = ((uint16_t)dat[0]<<8)|dat[1];
    *temperature = -45 + 175*((float)recv_temperature/65535);
    
    /* 转换湿度数据 */
    recv_humidity = ((uint16_t)dat[3]<<8)|dat[4];
    *humidity = 100 * ((float)recv_humidity / 65535);
    
    return 0;
}

TM1640.C

/*
 * TM1640.c
 *
 *  Created on: May 22, 2022
 *      Author: LDSCITECHE
 */
#include "TM1640.h"
#include "delay.h"

unsigned char CODE[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7c,0x39,0x5e,0x79,0x71};  //共阴数码管0~F字型码



/*
 ******************************************************************************
 * @brief     ( описание ):  функция для старта ( активации ) передачи данных на модуль
 * @param    ( параметры ):
 * @return  ( возвращает ):

 ******************************************************************************
 */
void TM1640_Generate_START(void)
{

    TM_SCL_H()
    ;
    TM_SDA_H()
    ;
    delay_us( DELAY_TIME);
    TM_SDA_L()
    ;
}
//----------------------------------------------------------------------------------

/*
 ******************************************************************************
 * @brief     ( описание ):  функция для конца отправки данных на модуль ( вызываеться по завершению )
 * @param    ( параметры ):
 * @return  ( возвращает ):

 ******************************************************************************
 */
void TM1640_Generate_STOP(void) {

    TM_SCL_L()
    ;
    delay_us( DELAY_TIME);
    TM_SDA_L()
    ;
    delay_us( DELAY_TIME);
    TM_SCL_H()
    ;
    delay_us( DELAY_TIME);
    TM_SDA_H()
    ;
}

/*
 ******************************************************************************
 * @brief     ( описание ):  функция отправки данных на модуль 1 байт
 * @param    ( параметры ):  1 байт информации
 * @return  ( возвращает ):

 ******************************************************************************
 */
void TM1640_WriteData(uint8_t oneByte) {

    delay_us( DELAY_TIME);

    for (uint8_t q = 0; q < 8; q++) {
        TM_SCL_L()
        ;

        if (oneByte & (1 << q)) {
            TM_SDA_H()
            ;
        } else {
            TM_SDA_L()
            ;
        }

        delay_us( DELAY_TIME);
        TM_SCL_H()
        ;
        delay_us( DELAY_TIME);
    }

    TM_SCL_L()
    ;
    delay_us( DELAY_TIME);
    TM_SDA_L()
    ;
    while( read_DIN ) {};
}





//void TM1640_Handle(void)
//{
//    u8 i;
//    TM1640_Generate_START();
//    TM1640_WriteData(0x40);//数据命令设置:普通模式,地址自动加一
//    TM1640_Generate_STOP();
//    TM1640_Generate_START();
//    TM1640_WriteData(0xC0);////地址命令设置:初始地址00H
//    for(i=3;i<16;i++)  //发送16位显示数据
//    {
//        TM1640_WriteData(CODE[i]);
//    }
//    TM1640_Generate_STOP();

//    TM1640_Generate_START();
//    TM1640_WriteData(0x8c);    //显示控制:显示开,脉冲宽度设为11/16
//    TM1640_Generate_STOP();
//    delay_ms(10);

//}


void disp(unsigned char add, unsigned char Num)
{
   TM1640_Generate_START();
   TM1640_WriteData(0x44);                        //数据命令设置:固定地址,写数据到显示寄存器
   TM1640_Generate_STOP(); 

   TM1640_Generate_START();
   TM1640_WriteData(add);                        //地址命令设置:写入add对应地址

   TM1640_WriteData(Num);                        //给add地址写数据
   TM1640_Generate_STOP();

  TM1640_Generate_START();
   TM1640_WriteData(0x89);                       //显示控制命令:开显示,脉冲宽度为11/16.
   TM1640_Generate_STOP();

}


//*************************************  
// 函数名称:TM1640_Init  
// 函数功能:TM1640设备初始化  
// 入口参数:0 1 代表 显示关 显示开  
// 出口参数:无  
//***************************************/  
void cls_TM1640(void)//清屏函数
{
    unsigned char h;    
    TM1640_WriteData(0x40);//连续地址模式
    TM1640_Generate_START();
    TM1640_WriteData(0xc0);
    for(h=0;h<16;h++)
    TM1640_WriteData(0x00);
    TM1640_Generate_STOP(); 
}

void TM1640_GPIO_INIT(void)
{
     cls_TM1640();
     TM1640_Generate_START();//数据写模式设置  
   TM1640_WriteData(0x80);//显示关控制   
   TM1640_Generate_STOP();     
}

至此,大致完成,最后再附上BOOM表以供欣赏:

采集端:
处理端: