基于 LoRa 的智慧农业系统的原型开发

更新时间Invalid date

概况

随时智能化的普及,利用物联网等信息技术改造传统农业,对农业生产要素进行数字化设计、智能化控制也快速发展了起来。为了提高农业的产量以及改善农业生态环境,提高生产经营效率,方便人们日常生活,我们设计了一种基于 LoRa 远距离传输的更加自动化、智能化、人性化的智慧农业方案。

主要功能点

  • 可以使用 App 进行远程控制设备的运行。
  • 控制屏进行本地控制设备的运行。
  • 可以实时远程观看所需数据情况。

硬件框图

软件业务流程

步骤

  • 第 1 步:硬件方案设计

    主控部分

    发送部分采用 GD32E230C8T6 的一款单片机,接收部分采用的是 ST 的一款 NUCLEO-L476RGDE 开发板。

    发送部分

    接收部分:

    • 原理图 如下所示

      注意:下面原理图只截取了部分,完整的原理图请参照上面的下载链接。

    • PCB 如下所示:

    传感器部分

    照度检测

    光照度检测我们选取一个 BH1750 照度检测模组来实现。BH1750 照度检测模组搭载一个 BH1750FVI,是 I2C 总线接口的数字环境光传感器 IC。可以准确读取 1-65535XL 的环境照度。照度检测相关内容请 点击下载

    原理图

    管脚介绍

    名称 VCC GND SCL SDA ADDR
    功能描述 3~5V 供电 参考地 IIC 时钟线 IIC 数据线 地址线

    温湿度传感器

    温湿度检测我们选取涂鸦的 SHT30 模组来实现。涂鸦三明治温湿度传感功能板为三明治开发板的应用部分,方便开发者快速实现温湿度硬件产品原型的一款开发板。功能板主要包含一颗 SENSIRION 温湿度传感器 SHT30-DIS,通过 I2C 协议进行通信,I2C 时钟频率最高支持 1MHz。

    关键器件介绍

    器件 说明
    U1(SHT30-DIS) SENSIRION 温湿度传感器,工作电压 2.4~5.5V,湿度精度 ±2%RH,温度精度 ±0.3℃,封装 8 脚 DFN

    涂鸦三明治温湿度传感功能板需要用到的管脚介绍

    I/O 说明
    VCC 电源供电脚
    GND 电源参考地
    SCL I2C 时钟信号
    SDA I2C 数据信号
    INT 告警信号,预留

    电源技术要求

    • 电源供电电压参照传感器工作电压范围:2.4~5.5V
    • 非测量状态典型电流:0.2uA
    • 低功耗连续测量模式典型电流:800uA

    原理图

    涂鸦三明治温湿度传感功能板的原理图如下所示:

    PCB

    ​涂鸦三明治温湿度传感功能板的 PCB 如下图所示:

    注意事项

    • 功能板为应用部分,需配合控制板与电源板使用。
    • 电源接口不要触碰 I/O 管脚,避免击穿模组对应 I/O 口。
    • 传感器本体附着灰尘与油污等会导致测量精度下降。
    • 传感器本体不能与清洁剂接触,例如洗板水。
    • 不能使用会释放化学分子的材料包装,否则可能受污染导致数据偏移或完全损坏。

    无线通信部分

    LoRa 通信发送部分

    LoRa 通信发送部分采用的是 WPG 公司的LLCC68的芯片。该芯片和 SX1268 管脚兼容。此次设计没有使用开关芯片来进行发送与接收模式的切换。直接使用双天线,采用半双工的通信方式。

    LoRa 通信接收部分

    ​LoRa 通信接收部分采用的是WPG公司的 SX1268 模组。

    注意:SX1268 和 LLCC68 管脚兼容,但参数设置有些区别。SX1268 扩频因子可以支持到 SF12,但 LLCC68 只能到 SF11。

    SX1268 参数如下图:

    LLCC68 参数如下图:

    WB3S 通信

    WB3S是由涂鸦智能开发的一款低功耗嵌入式 Wi-Fi+蓝牙 BLE 双协议云模组。它由一个高集成度的无线射频芯片 BK7231T 和少量外围器件构成,内置了 Wi-Fi 网络协议栈和丰富的库函数。MCU 通过串口和 WB3S 进行通信,采用透传的模式。

    控制屏部分

    显示控制部分采用的是迪文的 4.3 寸串口屏。

    正面图:

    ​背面图:

    接口图:

    此次设计中界面设计的主界面效果如下图:

    MCU 和显示屏通过串口通信,来实现控制和显示。

  • 第 2 步:创建产品

    1. 登录 涂鸦 IoT 开发平台

    2. 单击 创建产品,并在 标准类目 区域选择 电工 > 开关

      说明:您也可以从其他品类中去创建产品。

    3. 选择 自定义方案 后,输入产品信息并选择通讯协议为 Wi-Fi+蓝牙

    4. 单击 创建产品

    5. 根据要实现的设备功能,创建好 DP 功能点。您可以参考下图的 DP 点进行创建。

    6. 设定完功能点后,单击 下一步 选择设备面板。

      说明 推荐选择自由配置面板,使用起来比较直观,方便灵活。

    至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。

  • 第 3 步:软件方案设计

    发送部分

    发送部分即 GD32 采集传感器数据通过 LoRa 发送出去。

    程序设计入口

    打开 Demo 例程,其中 GD32_LoRa_TRANSMIT 文件夹内就是 demo 的应用代码。应用代码结构如下:

    ├── Application
    │   ├── main.c
    │   ├── gd32e23x_it.c
    │   ├── systick.c
    │   ├── gd32e23x_it.h
    │   ├── systick.h
    │   ├── gd32e23x_libopt.h
    ├── GD32E23x_Firmware_Library
    │   ├── CMSIS
            ├── Include
               │   ├──gd32e23x.h
               │   ├──system_gd32e23x.h
            ├── Source
               │   ├──startup_gd32e23x.s
               │   ├──system_gd32e23x.h        
    │   ├── GD32E23x_standard_peripheral
            ├── Include
            ├── Source
    ├──User
    │   ├── BH1750.c
    │   ├── BH1750.h
    │   ├──delay.c
    │   ├──delay.h
    │   ├──sht3x.c
    │   ├──sht3x.h
    │   ├──soft_i2c.c
    │   ├──soft_i2c.h
    │   ├──SPI.c
    │   ├──SPI.h
    │   ├──sx126x_v01.c
    │   ├──sx126x_v01.h
    │   ├──usart.c
    └──────usart.h 
     
    

    光照度传感器的驱动

    ​为了检测光照度,选用的传感器型号为 BH1750,通过 I2C 协议与 GD32 进行通信,相关接口封装都在 BH1750.c 文件中。模组具体使用流程如下:

    调用 Init_BH1750 初始化模组:

    //初始化 BH1750,根据需要请参考 pdf 进行修改****
    void Init_BH1750()
    {   
      /*开启 GPIOB 的外设时钟*/ 
    rcu_periph_clock_enable(RCU_GPIOB);   
    gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_6|GPIO_PIN_7); 
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6|GPIO_PIN_7); 	
    Single_Write_BH1750(0x01);  
    Delay_ms(180);    //延时 180ms
    }
    

    调用 mread 连续读出 BH1750 内部数据:

    //连续读出 BH1750 内部数据
    void mread(void)
    {   
      uchar i;	
      BH1750_Start();                        //起始信号
      BH1750_SendByte(SlaveAddress+1);       //发送设备地址+读信号
     for (i=0; i<3; i++)                     //连续读取 6 个地址数据,存储中 BUF
      {
        BUF[i] = BH1750_RecvByte();          //BUF[0]存储 0x32 地址中的数据
       if (i == 3)
       {
        BH1750_SendACK(1);                  //最后一个数据需要回 NOACK
       }
       else
       {		
        BH1750_SendACK(0);                  //回应 ACK
       }
      }
      BH1750_Stop();                        //停止信号
      Delay_ms(5);
    }
    

    调用 read_BH1750 获取光照强度值:

    uint16_t read_BH1750(void)
    {
        int dis_data;                        //变量	
    	float temp1;
    	float temp2;
    	Single_Write_BH1750(0x01);   // power on
        Single_Write_BH1750(0x10);   // H- resolution mode
        Delay_ms(180);            //延时 180ms
    	mread();       //连续读出数据,存储在 BUF 中
        dis_data=BUF[0];
        dis_data=(dis_data<<8)+BUF[1]; //合成数据 
    	temp1=dis_data/1.2;
    	temp2=10*dis_data/1.2;	
    	temp2=(int)temp2%10;	
    	return (uint16_t)temp1;
    }
    

    温湿度传感器的驱动

    我们选取涂鸦 SHT30 温湿度模组来测量温湿度。该模组通过 I2C 协议与 GD32 进行通信,I2C 时钟频率最高支持 1MHz。

    调用 SHT3x_reset 复位模组:

    /**
    * @brief   复位 SHT3x
    * @param   none
    * @retval  none
    */
    void SHT3x_reset(void)
    {
       SHT3x_Send_Cmd(SOFT_RESET_CMD);
       delay_1ms(20);
    }
    

    调用 SHT3x_Init 初始化模组:

    /* 描述:SHT3x 初始化函数,并将其设置为周期测量模式
     * 参数:无
     * 返回值:初始化成功返回 0,初始化失败返回 1 */
     uint8_t SHT3x_Init(void)
       {
         uint8_t ret;
         IIC_Init();
         IIC_Start();
         ret = SHT3x_Send_Cmd(MEDIUM_2_CMD);
         IIC_Stop();
         return ret;
       }
    

    调用 SHT3x_Get_Humiture_periodic 获取温湿度值:

    /* 描述:温湿度数据获取函数,周期读取,注意,需要提前设置周期模式   
    * 参数 Tem_val:存储温度数据的指针, 温度单位为°C
    * 参数 Hum_val:存储湿度数据的指针, 温度单位为%
    * 返回值:0-读取成功,1-读取失败
    ********************************************************************/
    uint8_t SHT3x_Get_Humiture_periodic(double *Tem_val,double *Hum_val)
    {
       uint8_t ret=0;
       uint8_t buff[6]={0};
       uint16_t tem,hum;
       double Temperature=0;
       double Humidity=0;
    
       IIC_Start();
       ret = SHT3x_Send_Cmd(READOUT_FOR_PERIODIC_MODE);	
       IIC_Start();
       ret = SHT3x_Recv_Data(6,buff);
       IIC_Stop();
       
       /* 校验温度数据和湿度数据是否接收正确 */
       if(CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5])
       {	
       	printf("CRC_ERROR,ret = 0x%x\r\n",ret);
       	return 1;
       }
       	
       /* 转换温度数据 */
       tem = (((uint16_t)buff[0]<<8) | buff[1]);//温度数据拼接
       Temperature= (175.0*(double)tem/65535.0-45.0) ;	// T = -45 + 175 * tem / (2^16-1)
       
       /* 转换湿度数据 */
       hum = (((uint16_t)buff[3]<<8) | buff[4]);//湿度数据拼接
       Humidity= (100.0*(double)hum/65535.0);			// RH = hum*100 / (2^16-1)
       
       /* 过滤错误数据 */
       if((Temperature>=-20)&&(Temperature<=125)&&(Humidity>=0)&&(Humidity<=100))
       {
       	*Tem_val = Temperature;
       	*Hum_val = Humidity;
       	
       	return 0;
       }
       else
       	return 1;
    }
    

    LoRa 芯片驱动

    为了实现远距离,低功耗的传输数据,我们采用的是WPG公司的 LLCC68 的芯片。通过 SPI 协议与 GD32 进行通信。采用半双工的通信方式。

    调用 RadioInit 初始化模组:

    //////////RADIO 层 ///////
    void RadioInit(void)
    {
      //RadioEvents = events;//这里进行了函数的初始化
    #ifdef USE_TCXO
    printf("USE TCXO\n");
    #else
    printf("USE CRYSTAL\n");
    #endif
      SX126xInit();////中断的回调函后续在其他地方去定义
      SX126xSetStandby( STDBY_RC );
      SX126xSetRegulatorMode( USE_DCDC);//USE_LDO//USE_DCDC
    
      SX126xSetBufferBaseAddress( 0x00, 0x00 );
      SX126xSetTxParams( 0, RADIO_RAMP_200_US );
      //DIO_0 的中断 MASK 全部打开,在 RadioSend()会再继续细分中断
      SX126xSetDioIrqParams( IRQ_RADIO_ALL, IRQ_RADIO_ALL, IRQ_RADIO_NONE, IRQ_RADIO_NONE );
    }
    

    调用 SX126xOnDio1Irq 进行数据处理:

    //DIO1 的中断函数
    void SX126xOnDio1Irq(void)
    {
    	 uint16_t irqRegs = SX126xGetIrqStatus( );
    	 SX126xClearIrqStatus( IRQ_RADIO_ALL );//这里清掉中断标志
    		 //发送结束
     if( ( irqRegs & IRQ_TX_DONE ) == IRQ_TX_DONE )
    			{
    				 TXDone=true;
    				 gpio_bit_toggle(LED_GPIO_Port, LED_Pin);
    				 OnTxDone();
    			}
    
    	//在 SX126xSetTx()设置了一个超时时间 可以检测改功能 --ok
    	if( ( irqRegs & IRQ_RX_TX_TIMEOUT ) == IRQ_RX_TX_TIMEOUT )
    			{
    				 TimeOutFlag=true;
    				 printf(" RX/TX timeout\n");
    		
    			}
    	if( ( irqRegs & IRQ_RX_DONE ) == IRQ_RX_DONE )
    	{
    			SX126xGetPayload( RadioRxPayload, &RadioRxPacketSize , 255 );
    			SX126xGetPacketStatus( &RadioPktStatus );
    			gpio_bit_toggle(LED_GPIO_Port, LED_Pin);
    			OnRxDone();
    			RXDoneFlag=true;
    	}
    
    	 if( ( irqRegs & IRQ_CRC_ERROR ) == IRQ_CRC_ERROR )
    		 {
    				printf("CRC fail\n");
    				CRCFail=true;
    			 
    		 }
    	if( ( irqRegs & IRQ_CAD_DONE ) == IRQ_CAD_DONE )
    			{
    				if ( ( irqRegs & IRQ_CAD_ACTIVITY_DETECTED ) == IRQ_CAD_ACTIVITY_DETECTED ) 
    				{
    					//printf("IRQ_CAD_ACTIVITY_DETECTED\n");	
    					//CadDetect=true;
    				}      
    			}
    	if( ( irqRegs & IRQ_PREAMBLE_DETECTED ) == IRQ_PREAMBLE_DETECTED )
    	{
    			__NOP( );
    	}
    
    	if( ( irqRegs & IRQ_SYNCWORD_VALID ) == IRQ_SYNCWORD_VALID )
    	{
    			__NOP( );
    	}
    
    	if( ( irqRegs & IRQ_HEADER_VALID ) == IRQ_HEADER_VALID )
    	{
    			__NOP( );
    	}
    }
    

    主程序设计

    #define LoRa_MODE	1
    #define FSK_MODE  0
    #define TRANSMITTER 1
    #define RECEIVER   0
    int main(void)
    {
      uint8_t i=0;
      uint16_t  light;
      double Tem_val,Hum_val;
      bool DetectTruetable[100]={0};//CAD 成功的分布
      bool RXTruetable[100]={0};//CAD 后能接收正确的分布
      uint8_t CadDetectedTime=0;//检测到的 cad 的次数
      uint8_t RxCorrectTime=0;//RX 接收正确次数
      uint8_t TxTime=0;		//TX 次数
      int random_number=0;
      RadioStatus_t RadioStatus;
      //连续发送的时候用
      uint8_t ModulationParam[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
      uint8_t PacketParam[9] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
      /* Configure the system clock */
      systick_config(); 
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      rcu_config();
      gpio_config();
      spi_config();
      USART0_Init();
      USART1_Init();
      Init_BH1750();
      SHT3x_reset();
      if( 0 == SHT3x_Init())
          printf("SHT3x_Init OK \r\n");
      else
    	printf("SHT3x_Init ERR \r\n");
    	
      gpio_bit_toggle(LED_GPIO_Port, LED_Pin);
      Delay_ms(250);
    	
      SX126xReset();
      i=SX126xReadRegister(REG_LR_CRCSEEDBASEADDR);
    
      if(i==0x1D)
      {   
    	printf("SPI SUCCESS!\n\r");
      }
      else
      {
    	printf("SPI Fail! REG_LR_CRCSEEDBASEADDR=%x\n\r",i);
      }
      RadioInit();
      SX126xWriteRegister(0x889, SX126xReadRegister(0x889) & 0xfB);//SdCfg0 (0x889) sd_res (bit 2) = 0 
      printf("RadioInit Done!\n\r");
    while (1)
      {
    #if (TRANSMITTER==1)
    	while(1)
    	{ 
            light =read_BH1750();
    			/* 采集温湿度数据 */
    			if(SHT3x_Get_Humiture_periodic(&Tem_val,&Hum_val) == 0)
    			{
    				memcpy(Buffer,(double*)(&Tem_val),8);		
    				memcpy(Buffer+8,(double*)(&Hum_val),8);
    			}
    			else
    			    printf("Get_Humiture ERR\r\n");
    			
    		memcpy(Buffer+16, (uint16_t*)&light, sizeof((uint16_t*)&light));
    		RadioSend(Buffer ,18);
    		printf("1=%d\n",read_BH1750());	
    		while(TXDone==false && TimeOutFlag==false);//一直等待 tx done
    		TXDone=false;
    		TimeOutFlag=false;
    		printf("TxTime=%d\n",TxTime);
    		Delay_ms(1000); //1s
    		//读取状态
    		RadioStatus=SX126xGetStatus();
    		printf("RadioStatus is(after TX_DONE) %d\n",(((RadioStatus.Value)>>4)&0x07));	
    	}
    #elif (RECEIVER==1) 
      while(1)
    	{
    
    #if (RX_CONTINOUS==1)
    		//开始接收
    		RadioRx(0xFFFFFF);//50MS(0XC80)超时  0-单次接收 无超时
    		printf("continous RX...\n");
    		while(1);//连续接收
    #endif
    		RadioRx(2000);//50MS(0XC80)超时  0-单次接收 无超时
    		while(RXDoneFlag==false && TimeOutFlag==false && CRCFail==false);
    		if(RXDoneFlag==true || TimeOutFlag==true || CRCFail==true)
    		{
    			if(CRCFail==false)	//CRC 无错误
    			{
    				if(RXDoneFlag==true)
    				{
    					printf("\n%d:RxCorrect-PING\n",RxCorrectTime);
    					RxCorrectTime++;
    				}
    			}
    			CRCFail=false;
    			RXDoneFlag=false;
    			TimeOutFlag=false;
    		}
    		
    	}
    #endif		
     }
    }
    

    本 demo 发送部分完整工程:[点此下载](Tuya-Community/tuya-iotos-embeded-mcu-demo-wifi-ble-GD32_LoRa_TRANSMIT (github.com))

    接收部分

    接收部分即 STM32 收到 LoRa 模组的数据进行处理控制。

    程序设计入口

    打开 Demo 例程,其中 STM32_LoRa_LCD_RECEIVE 文件夹内就是 Demo 的应用代码。应用代码结构如下:

    ├── Src
    │   ├── main.c
    │   ├── connect_wifi.c
    │   ├── delay.c
    │   ├── lcd.c
    │   ├── myOS.c
    │   ├── stm32l4xx_hal_msp.c
    │   ├── stm32l4xx_it.c
    │   ├── sx126x_v01.c
    │   ├── system_stm32l4xx.c
    │   ├── time.c
    │   ├──usart.c
    ├── Inc
    │   ├── main.h
    │   ├── connect_wifi.h
    │   ├── delay.h
    │   ├── lcd.h
    │   ├── myOS.h
    │   ├── stm32l4xx_hal_conf.h
    │   ├── stm32l4xx_it.h
    │   ├── sx126x_v01.h
    │   ├── time.h
    │   ├── type.h
    │   ├──usart.h
    ├── Drivers
    │   ├── CMSIS
            ├── Device
               │   ├──STM32L4xx
            ├── DSP_Lib
               │   ├──Source
            ├── Include
            ├── Lib
               │   ├──ARM
               │   ├──GCC
            ├── RTOS
               │   ├──Template
    │   ├── STM32L4xx_HAL_Driver
            ├── Inc
            ├── Src
    └── MCU_SDK
        ├── mcu_api.c
        ├── mcu_api.h
        ├── protocol.c
        ├── protocol.h
        ├── system.c
        ├── system.h
        └── wifi.h  
    

    LoRa 模组驱动

    本 Demo 采用的是 WPG 公司的 SX1268 模组,通过 SPI 协议与 STM32 进行通信。采用半双工的通信方式。驱动同 LLCC68,但二者参数设置有些差别。差别如下所示:

    SX1268 扩频因子可以支持到 SF12,但 LLCC68 只能到 SF11。为了适用这两款芯片,同时满足要求,此处扩频因子选择 SF10。

    
    #define LoRa_BANDWIDTH                              1       // [0: 125 kHz,
    															//	1: 250 kHz, 													
    															//	2: 500k
    															//	3 :20.83kHz
    															//	4:31.25kHz
    															//	5:62.5kHz4
    															//6:41.67
    #define LoRa_SPREADING_FACTOR                       10      // [SF7..SF12]
    
    

    控制屏程序设计

    STM32 通过串口和控制屏进行通信,波特率 115200。

    界面设计

    首先采用 PS 软件做出自己需要的图片,然后保存成 800*480 分辨率的 BMP 图片格式。接着采用迪文的一款上位机软件进行显示和控制设计。

    温湿度界面:

    image-20210823163804336

    image-20210824152538656

    光照度界面:

    image-20210824153113907

    image-20210823164138158

    节点控制界面:

    image-20210823164537015

    image-20210824152634111

    控制屏界面设计完整工程:点此下载

    驱动程序设计

    调用 WriteDataToLCD 对控制屏写入数据:

    
    /*******************************************************************************
    ** Function Name  :void WriteDataToLCD(uint16_t startAddress,uint16_t return_data_start_addr,uint16_t length)
    ** Description    : 数据写入触摸屏变量寄存器
    ** Input          : uint16_t startAddress,uint16_t return_data_start_addr,uint16_t length
    ** Output         : None
    ** Return         : None
    ** Attention		 	: 
    *******************************************************************************/
    void WriteDataToLCD(uint16_t startAddress,uint16_t return_data_start_addr,uint16_t length)
    {
     /*命令的长度由帧头(2 个字节)+数据长度(1 个字节)+指令(1 个字节)+起始地址(2 个字节)+数据(长度为 length)*/
       uint8_t i;
       usart1_txBuf[0]=0x5a;
       usart1_txBuf[1]=0xa5;
       usart1_txBuf[2]=length+3;
       usart1_txBuf[3]=0x82;
       usart1_txBuf[4]=(uint8_t)((startAddress>>8)&0xff);//起始地址
       usart1_txBuf[5]=(uint8_t)(startAddress&0XFF);//起始地址
       for(i=0;i<length;i++)
       {
       	usart1_txBuf[i+6]=((SEND_BUF[i+return_data_start_addr]));
       }
       HAL_UART_Transmit(&huart1, usart1_txBuf, length+6, 20);
    }
    
    

    调用 ReadDataFromLCD 读取控制屏数据:

    
    /*******************************************************************************
    ** Function Name  :void ReadDataFromLCD(uint16_t startAddress,uint8_t readWordLength)
    ** Description    : 读变量存储器数据
    ** Input          : uint16_t startAddress,uint8_t readWordLength
    ** Output         : None
    ** Return         : None
    ** Attention		 	: 
    *******************************************************************************/
    void ReadDataFromLCD(uint16_t startAddress,uint16_t readWordLength)
    {
     //命令的长度由帧头(2 个字节)+数据长度(1 个字节)+指令(1 个字节)+起始地址(2 个字节)+读取的字长度(1 个字节)
       usart1_txBuf[0]=0x5a;
       usart1_txBuf[1]=0xa5;
       usart1_txBuf[2]=0x04;
       usart1_txBuf[3]=0x83;
       usart1_txBuf[4]=(uint8_t)((startAddress>>8)&0xff);//起始地址
       usart1_txBuf[5]=(uint8_t)(startAddress&0xff);//起始地址
       usart1_txBuf[6]=readWordLength;//读取长度
    
       HAL_UART_Transmit(&huart1, usart1_txBuf, 7 , 20);
    }
    
    

    调用 void send_tz 控制页面跳转:

    
    /*******************************************************************************
    ** Function Name  :void send_tz(void))
    ** Description    : 跳转页面函数
    ** Input          : None
    ** Output         : None
    ** Return         : None
    ** Attention		 	: 
    *******************************************************************************/
    void send_tz(void)
    {
       uint8_t i;
       usart1_txBuf[0]=0x5a;
       usart1_txBuf[1]=0xa5;
       usart1_txBuf[2]=0x07;
       usart1_txBuf[3]=0x82;
       usart1_txBuf[4]=0x00;
       usart1_txBuf[5]=0x84;
       usart1_txBuf[6]=0x5a;
       usart1_txBuf[7]=0x01;
       for(i=0;i<2;i++)
       {
       	usart1_txBuf[i+8]=((SEND_BUF[i]));
       }
       HAL_UART_Transmit(&huart1, usart1_txBuf, 10, 20);
    }
    
    

    WB3S 模组

    STM32 通过串口和 WB3S 进行通信,采用透传的模式。

    调用 wifi_protocol_init 初始化模组串口协议:

    
    /**
     * @brief  协议串口初始化函数
     * @param  Null
     * @return Null
     * @note   在 MCU 初始化代码中调用该函数
     */
    void wifi_protocol_init(void)
    {
        //#error " 请在 main 函数中添加 wifi_protocol_init()完成 wifi 协议初始化,并删除该行"
        rx_buf_in = (unsigned char *)wifi_uart_rx_buf;
        rx_buf_out = (unsigned char *)wifi_uart_rx_buf;
        
        stop_update_flag = DISABLE;
        
    #ifndef WIFI_CONTROL_SELF_MODE
        wifi_work_state = WIFI_SATE_UNKNOW;
    #endif
    }
    
    

    调用 wifi_uart_service 对串口数据进行处理:

    /**
    * @brief  wifi 串口数据处理服务
    * @param  Null
    * @return Null
    * @note   在 MCU 主函数 while 循环中调用该函数
    */
    void wifi_uart_service(void)
    {
       //#error "请直接在 main 函数的 while(1){}中添加 wifi_uart_service(),调用该函数不要加任何条件判断,完成后删除该行" 
       static unsigned short rx_in = 0;
       unsigned short offset = 0;
       unsigned short rx_value_len = 0;
       
       while((rx_in < sizeof(wifi_data_process_buf)) && with_data_rxbuff() > 0)
       {
           wifi_data_process_buf[rx_in ++] = take_byte_rxbuff();
       }
       
       if(rx_in < PROTOCOL_HEAD)
           return;
       
       while((rx_in - offset) >= PROTOCOL_HEAD) {
           if(wifi_data_process_buf[offset + HEAD_FIRST] != FRAME_FIRST)
           {
               offset ++;
               continue;
           }
           
           if(wifi_data_process_buf[offset + HEAD_SECOND] != FRAME_SECOND)
           {
               offset ++;
               continue;
           }  
           
           if(wifi_data_process_buf[offset + PROTOCOL_VERSION] != MCU_RX_VER) 
           {
               offset += 2;
               continue;
           }      
           
           rx_value_len = wifi_data_process_buf[offset + LENGTH_HIGH] * 0x100;
           rx_value_len += (wifi_data_process_buf[offset + LENGTH_LOW] + PROTOCOL_HEAD);
           if(rx_value_len > sizeof(wifi_data_process_buf) + PROTOCOL_HEAD)
           {
               offset += 3;
               continue;
           }
           
           if((rx_in - offset) < rx_value_len) 
           {
               break;
           }
           
           //数据接收完成
           if(get_check_sum((unsigned char *)wifi_data_process_buf + offset,rx_value_len - 1) != wifi_data_process_buf[offset +                rx_value_len - 1])
           {
               offset += 3;
               continue;
           }
           
           data_handle(offset);
           offset += rx_value_len;
          }//end while
    
       rx_in -= offset;
       if(rx_in > 0) 
       {
         my_memcpy((char *)wifi_data_process_buf, (const char *)wifi_data_process_buf + offset, rx_in);
       }
    }
    

    调用以下 DP 处理函数对控制屏和 GPIO 进行控制:

    /*****************************************************************************
    函数名称 : dp_download_switch_1_handle
    功能描述 : 针对 DPID_SWITCH_1 的处理函数
    输入参数 : value:数据源数据
            : length:数据长度
    返回参数 : 成功返回:SUCCESS/失败返回:ERROR
    使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至 app
    *****************************************************************************/
    static unsigned char dp_download_switch_1_handle(const unsigned char value[], unsigned short length)
    {
        //示例:当前 DP 类型为 BOOL
        unsigned char ret;
        //0:关/1:开
        unsigned char switch_1;
        
        switch_1 = mcu_get_dp_download_bool(value,length);
        if(switch_1 == 0)
    		 {
                    //开关关
    				SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    			
    				SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x00; 
    				WriteDataToLCD(0x1000,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_RESET); 
            }
    		else 
    		{
    				SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    
    				SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x01; 
    				WriteDataToLCD(0x1000,0,2);
    			    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
        	       //开关开
          }
      
        //处理完 DP 数据后应有反馈
        ret = mcu_dp_bool_update(DPID_SWITCH_1,switch_1);
        if(ret == SUCCESS)
            return SUCCESS;
        else
            return ERROR;
    }
    /*****************************************************************************
    函数名称 : dp_download_switch_2_handle
    功能描述 : 针对 DPID_SWITCH_2 的处理函数
    输入参数 : value:数据源数据
            : length:数据长度
    返回参数 : 成功返回:SUCCESS/失败返回:ERROR
    使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至 app
    *****************************************************************************/
    static unsigned char dp_download_switch_2_handle(const unsigned char value[], unsigned short length)
    {
        //示例:当前 DP 类型为 BOOL
        unsigned char ret;
        //0:关/1:开
        unsigned char switch_2;
        
        switch_2 = mcu_get_dp_download_bool(value,length);
        if(switch_2 == 0) {
    			  
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x00; 
    				WriteDataToLCD(0x1001,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET); 
            //开关关
        }else {
    			   		
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    				
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x01; 
    				WriteDataToLCD(0x1001,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET); 
            //开关开
        }
      
        //处理完 DP 数据后应有反馈
        ret = mcu_dp_bool_update(DPID_SWITCH_2,switch_2);
        if(ret == SUCCESS)
            return SUCCESS;
        else
            return ERROR;
    }
    /*****************************************************************************
    函数名称 : dp_download_switch_3_handle
    功能描述 : 针对 DPID_SWITCH_3 的处理函数
    输入参数 : value:数据源数据
            : length:数据长度
    返回参数 : 成功返回:SUCCESS/失败返回:ERROR
    使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至 app
    *****************************************************************************/
    static unsigned char dp_download_switch_3_handle(const unsigned char value[], unsigned short length)
    {
        //示例:当前 DP 类型为 BOOL
        unsigned char ret;
        //0:关/1:开
        unsigned char switch_3;
        
        switch_3 = mcu_get_dp_download_bool(value,length);
        if(switch_3 == 0) {
    			  		
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    				
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x00; 
    				WriteDataToLCD(0x1002,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_RESET); 
            //开关关
        }else {
    			
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x01; 
    				WriteDataToLCD(0x1002,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_SET); 
            //开关开
        }
      
        //处理完 DP 数据后应有反馈
        ret = mcu_dp_bool_update(DPID_SWITCH_3,switch_3);
        if(ret == SUCCESS)
            return SUCCESS;
        else
            return ERROR;
    }
    /*****************************************************************************
    函数名称 : dp_download_switch_4_handle
    功能描述 : 针对 DPID_SWITCH_4 的处理函数
    输入参数 : value:数据源数据
            : length:数据长度
    返回参数 : 成功返回:SUCCESS/失败返回:ERROR
    使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至 app
    *****************************************************************************/
    static unsigned char dp_download_switch_4_handle(const unsigned char value[], unsigned short length)
    {
        //示例:当前 DP 类型为 BOOL
        unsigned char ret;
        //0:关/1:开
        unsigned char switch_4;
        
        switch_4 = mcu_get_dp_download_bool(value,length);
        if(switch_4 == 0) {
    			   
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    				
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x00; 
    				WriteDataToLCD(0x1003,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET); 
            //开关关
        }else {
    			
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x06; 
    				send_tz();
    			
    			    SEND_BUF[0]=0x00;
    				SEND_BUF[1]=0x01; 
    				WriteDataToLCD(0x1003,0,2);
    			  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET); 
            //开关开
        }
      
        //处理完 DP 数据后应有反馈
        ret = mcu_dp_bool_update(DPID_SWITCH_4,switch_4);
        if(ret == SUCCESS)
            return SUCCESS;
        else
            return ERROR;
    }
    

    按键配网

    调用 Connect_Wifi 进行配网设置,通过 WB3S 模组连接涂鸦云平台。

    
    void Connect_Wifi(void)
    {
     if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(WIFI_KEY_GPIO_Port, WIFI_KEY_Pin))
    		{
    			delay_ms(10);
    			if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(WIFI_KEY_GPIO_Port, WIFI_KEY_Pin))
    				{
    					mcu_set_wifi_mode(0);
    					printf("begin connect wifi\r\n");
    				}
    		}
    	switch(mcu_get_wifi_work_state())
    	{
    		 case SMART_CONFIG_STATE:       
    					 printf("smart config\r\n");
    				 HAL_GPIO_TogglePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin);
    				 delay_ms(250);
    			break;
    			case AP_STATE:        
    					 printf("AP config\r\n");
    					 HAL_GPIO_TogglePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin);
    					 delay_ms(500);
    			break;
    			case WIFI_NOT_CONNECTED: 
    					 printf("connect wifi\r\n");
    					 HAL_GPIO_WritePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin, GPIO_PIN_SET);	 
    			break;
    			case WIFI_CONNECTED:
    					 printf("connect success\r\n");
    					 HAL_GPIO_WritePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin, GPIO_PIN_SET);	 
    			case WIFI_CONN_CLOUD:
    					 HAL_GPIO_WritePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin, GPIO_PIN_SET);
    			break;
    			default:
    					 HAL_GPIO_WritePin(WIFI_LED_GPIO_Port, WIFI_LED_Pin, GPIO_PIN_RESET);	 
    					 printf ("connect fail\r\n");
    			break;
    				
    	}
    }
    
    

    系统管理函数接口设计

    由于使用串口较多,防止出现串口数据传输过程中被干扰的情况出现,此处利用定时器 3 中断,写了一个系统管理函数。不同定时时间处理不同的任务。

    //系统管理函数
    void System_Management(void)
    {
    	static __IO uint8_t timer_50ms = 0U;
    	static __IO uint8_t timer_500ms = 0U;
    	static __IO uint8_t timer_1000ms = 0U;
    	static __IO uint8_t timer_10s = 0U;
    
    	system_running_timer++;	
    	System_Run_2ms();		/*2ms task*/
    	timer_50ms++;
    	/*write if-else to aviod multiple task are running at same time
    	only running 2ms task and one of 50ms, 500ms and 1s every 2ms.*/
    	if(timer_50ms >= 25u)
    	{
    		timer_50ms = 0u;
    		Systen_Run_50ms();
    		
    		timer_500ms ++;
    	}else if(timer_500ms >= 10u)
    	{
    		timer_500ms = 0u;
    		System_Run_500ms();
    		
    		timer_1000ms ++;
    	}else if(timer_1000ms >= 2u)
    	{
    		timer_1000ms = 0u;
    		System_Run_1000ms();
    		
    		timer_10s ++;
    	}else
    	{
    	
    	}
    }
    

    调用 System_Run_2ms 和 Systen_Run_50ms 分别处理 WiFi 模组和 LoRa 模组的数据。

    /*
    	2ms task
    */
    void System_Run_2ms(void)
    {
    	system_running_timer += 2;  /*Record system running time*/	
        wifi_uart_service();//wifi 串口数据处理服务
        Connect_Wifi(); //配网  
    }
    
    /*
    	50ms task
    */
    void Systen_Run_50ms(void)
    { 
    	 OnRxDone();
    }
    
    

    主程序设计

    
    #define LoRa_MODE	1
    #define FSK_MODE   0
    #define TRANSMITTER    0
    #define RECEIVER       1
    
    int main(void)
    {
      uint8_t i=0;
      bool DetectTruetable[100]={0};//CAD 成功的分布
      bool RXTruetable[100]={0};//CAD 后能接收正确的分布
      uint8_t CadDetectedTime=0;//检测到的 cad 的次数
      uint8_t RxCorrectTime=0;//RX 接收正确次数
      uint8_t TxTime=0;		//TX 次数
    
    	//连续发送的时候用
      uint8_t ModulationParam[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
      uint8_t PacketParam[9] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
      /* Configure the system clock */
      SystemClock_Config();
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_SPI1_Init();
      MX_USART1_UART_Init();
      MX_USART2_UART_Init();
      MX_USART3_UART_Init();
      wifi_protocol_init();   //wifi 协议初始化
      TIM3_Init(20-1,8000-1); //定时器 3 初始化,定时 2ms
    	
      for(i=0;i<1;i++)
      {
    	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
    	HAL_Delay(1000);
    	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
    	HAL_Delay(1000);
      }
    	
      SX126xReset();
      i=SX126xReadRegister(REG_LR_CRCSEEDBASEADDR);
      if(i==0x1D)
      {   
    	printf("SPI SUCCESS!\n\r");
      }
      else
      {
    	printf("SPI Fail! REG_LR_CRCSEEDBASEADDR=%x\n\r",i);
      }
      RadioInit();
      SX126xWriteRegister(0x889, SX126xReadRegister(0x889) & 0xfB);//SdCfg0 (0x889) sd_res (bit 2) = 0 
      printf("RadioInit Done!\n\r");
    
    
    #if (TEST_MODE==0)   //infinite preamble TX mode
    	//连续发送
    	SX126xSetStandby( STDBY_RC );
    	SX126xSetPacketType(PACKET_TYPE_LoRa);//todo: 增加发射 FSK 模式下的改指令
    	
    	printf("set lora params\n");
    	ModulationParam[0]=LoRa_SPREADING_FACTOR;
    	ModulationParam[1]=Bandwidths_copy[LoRa_BANDWIDTH];
    	ModulationParam[2]=LoRa_CODINGRATE;
    	ModulationParam[3]=0;//1:SF11 and SF12 0:其他 低速率优化  
    	SX126xWriteCommand( RADIO_SET_MODULATIONPARAMS, ModulationParam, 4 );//lora 发射参数配置
    
    
    	//设置 lora 包参数
    	PacketParam[0]=(LoRa_PREAMBLE_LENGTH>>8)& 0xFF;
    	PacketParam[1]=LoRa_PREAMBLE_LENGTH;
    	PacketParam[2]=LoRa_FIX_LENGTH_PAYLOAD_ON;//head type
    	PacketParam[3]=0xFF;//0Xff is MaxPayloadLength
    	PacketParam[4]=true;//CRC on
    	PacketParam[5]=LoRa_IQ_INVERSION_ON;
    	SX126xWriteCommand( RADIO_SET_PACKETPARAMS, PacketParam, 6 );
    
    	//SX126xWriteBuffer( 0x00, SendData, 10 );
    
    	//连续发送 lora
    	SX126xSetRfFrequency( RF_FREQUENCY );
        SX126xSetRfTxPower( TX_OUTPUT_POWER );
    	SX126xSetTxInfinitePreamble();
    
    	printf("TxContinuousWave Now--infinite preamble!\n\r");
    	while(1);
    #elif (TEST_MODE==1) //TX CW
    
    	RadioSetTxContinuousWave( RF_FREQUENCY, TX_OUTPUT_POWER, TX_TIMEOUT );
    	printf("TxContinuousWave Now---CW!\n\r");
    	while(1);
    
    #endif
    
    
    #if (FSK_MODE==1)
    
    	SX126xSetRfFrequency(RF_FREQUENCY);
    	RadioSetTxConfig( MODEM_FSK, TX_OUTPUT_POWER, FSK_FDEV, FSK_BANDWIDTH,
    						FSK_DATARATE, 0,
    						FSK_PREAMBLE_LENGTH, FSK_FIX_LENGTH_PAYLOAD_ON,
    						true, 0, 0, 0, 3000 );
    	
    	RadioSetRxConfig( MODEM_FSK, FSK_BANDWIDTH, FSK_DATARATE,
    						0, FSK_AFC_BANDWIDTH, FSK_PREAMBLE_LENGTH,
    						0, FSK_FIX_LENGTH_PAYLOAD_ON, FSK_FIX_LENGTH_PAYLOAD, FSK_CRC,0, 0,false, RX_CONTINOUS );
    	
    	printf("FSK:%d,Fdev=%ld,BitRate=%ld,BW=%ld,PWR=%d,PreLen=%d,PYLOAD=%d\n\r",RF_FREQUENCY,FSK_FDEV,FSK_DATARATE,FSK_BANDWIDTH,TX_OUTPUT_POWER,FSK_PREAMBLE_LENGTH,BUFFER_SIZE);
    	printf("configure FSK parameters done\n!");
    
    	
    
    #elif (LoRa_MODE==1)
    
    			SX126xSetRfFrequency(RF_FREQUENCY);
    			RadioSetTxConfig( MODEM_LoRa, TX_OUTPUT_POWER, 0, LoRa_BANDWIDTH,
    									   LoRa_SPREADING_FACTOR, LoRa_CODINGRATE,
    									   LoRa_PREAMBLE_LENGTH, LoRa_FIX_LENGTH_PAYLOAD_ON,
    									   true, 0, 0, LoRa_IQ_INVERSION_ON, 3000 );
    
    
    		  RadioSetRxConfig( MODEM_LoRa, LoRa_BANDWIDTH, LoRa_SPREADING_FACTOR,
    										 LoRa_CODINGRATE, 0, LoRa_PREAMBLE_LENGTH,
    										 LoRa_SYMBOL_TIMEOUT, LoRa_FIX_LENGTH_PAYLOAD_ON,
    										 0, true, 0, 0, LoRa_IQ_INVERSION_ON, RX_CONTINOUS );//最后一个参数设置是否是连续接收
    
    
    		
    		  printf("LoRa:%d,SF=%d,codeRate=%d,BW=%d,PWR=%d,PreLen=%d,PYLOAD=%d\n\r",RF_FREQUENCY,LoRa_SPREADING_FACTOR,LoRa_CODINGRATE,LoRa_BANDWIDTH,TX_OUTPUT_POWER,LoRa_PREAMBLE_LENGTH,BUFFER_SIZE);
    		  if (RadioPublicNetwork.Previous==true && RadioPublicNetwork.Current==false)
    			printf("public\n\r");
    		  else if (RadioPublicNetwork.Previous==false && RadioPublicNetwork.Current==false)
    			printf("private\n\r");
    	    printf("configure LoRa parameters done\n!");
    		 
    #endif
    
    while (1)
      {
        
    #if (TRANSMITTER==1)
    
    	while(1)
    	{
    	    Buffer[0] = TxTime++;
    	    Buffer[1] = 1;
    	    Buffer[2] = 2;
    	    Buffer[3] = 3;
    	    Buffer[4] = 0;
    	    Buffer[5] = 0;
    		  RadioSend(Buffer,20);
    		while(TXDone==false && TimeOutFlag==false);//一直等待 tx done
    		TXDone=false;
    		TimeOutFlag=false;
    		printf("TxTime=%d\n",TxTime);
    		HAL_Delay(500); ///1s
    
    		//读取状态
    		RadioStatus=SX126xGetStatus();
    		printf("RadioStatus is(after TX_DONE) %d\n",(((RadioStatus.Value)>>4)&0x07));
    		
    		
    	}
    #elif (RECEIVER==1) 
     while(1)
    	{	
    #if (RX_CONTINOUS==1)
    	 //开始接收	
    	 RadioRx(0xFFFFFF);//50MS(0XC80)超时  0-单次接收 无超时
         printf("continous RX...\n");
         while(1);//连续接收
    #endif
     		RadioRx(2000);//50MS(0XC80)超时  0-单次接收 无超时
      	while(RXDoneFlag==false && TimeOutFlag==false && CRCFail==false);
    		if(RXDoneFlag==true || TimeOutFlag==true || CRCFail==true)
    		{
    			if(CRCFail==false)	//CRC 无错误
    			{
    				if(RXDoneFlag==true)
    				{
    					printf("\n%d:RxCorrect-PING\n",RxCorrectTime);
    					RxCorrectTime++;
    				}
    			}
    			CRCFail=false;
    			RXDoneFlag=false;
    			TimeOutFlag=false;
    		}
    	}
    #endif		  
     }
    }
    

    本 Demo 接收部分完整工程:点此下载

小结

至此智能农业场景搭建就完成了,它可以 App 远程控制、本地控制等。大家可以在此基础上实现尽可能多的功能。同时您可以基于涂鸦 IoT 平台丰富它的功能,也可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。