随时智能化的普及,利用物联网等信息技术改造传统农业,对农业生产要素进行数字化设计、智能化控制也快速发展了起来。为了提高农业的产量以及改善农业生态环境,提高生产经营效率,方便人们日常生活,我们设计了一种基于 LoRa 远距离传输的更加自动化、智能化、人性化的智慧农业方案。
发送部分采用 GD32E230C8T6 的一款单片机,接收部分采用的是 ST 的一款 NUCLEO-L476RGDE 开发板。
光照度检测我们选取一个 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 | 告警信号,预留 |
电源技术要求
原理图
涂鸦三明治温湿度传感功能板的原理图如下所示:
PCB
涂鸦三明治温湿度传感功能板的 PCB 如下图所示:
注意事项
- 功能板为应用部分,需配合控制板与电源板使用。
- 电源接口不要触碰 I/O 管脚,避免击穿模组对应 I/O 口。
- 传感器本体附着灰尘与油污等会导致测量精度下降。
- 传感器本体不能与清洁剂接触,例如洗板水。
- 不能使用会释放化学分子的材料包装,否则可能受污染导致数据偏移或完全损坏。
LoRa 通信发送部分采用的是 WPG 公司的LLCC68的芯片。该芯片和 SX1268 管脚兼容。此次设计没有使用开关芯片来进行发送与接收模式的切换。直接使用双天线,采用半双工的通信方式。
LoRa 通信接收部分采用的是WPG公司的 SX1268 模组。
注意:SX1268 和 LLCC68 管脚兼容,但参数设置有些区别。SX1268 扩频因子可以支持到 SF12,但 LLCC68 只能到 SF11。
SX1268 参数如下图:
LLCC68 参数如下图:
WB3S是由涂鸦智能开发的一款低功耗嵌入式 Wi-Fi+蓝牙 BLE 双协议云模组。它由一个高集成度的无线射频芯片 BK7231T 和少量外围器件构成,内置了 Wi-Fi 网络协议栈和丰富的库函数。MCU 通过串口和 WB3S 进行通信,采用透传的模式。
显示控制部分采用的是迪文的 4.3 寸串口屏。
正面图:
背面图:
接口图:
此次设计中界面设计的主界面效果如下图:
MCU 和显示屏通过串口通信,来实现控制和显示。
登录 涂鸦 IoT 开发平台。
单击 创建产品,并在 标准类目 区域选择 电工 > 开关。
说明:您也可以从其他品类中去创建产品。
选择 自定义方案 后,输入产品信息并选择通讯协议为 Wi-Fi+蓝牙。
单击 创建产品。
根据要实现的设备功能,创建好 DP 功能点。您可以参考下图的 DP 点进行创建。
设定完功能点后,单击 下一步 选择设备面板。
说明 推荐选择自由配置面板,使用起来比较直观,方便灵活。
至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。
发送部分即 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;
}
为了实现远距离,低功耗的传输数据,我们采用的是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
本 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 图片格式。接着采用迪文的一款上位机软件进行显示和控制设计。
温湿度界面:
光照度界面:
节点控制界面:
控制屏界面设计完整工程:点此下载
驱动程序设计
调用 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);
}
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 平台丰富它的功能,也可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。