全场景——(八)低成本 Modbus 传感器的实现

news2024/12/24 20:57:40

文章目录

  • 一、硬件资源介绍与接线
  • 二、创建与体验第 1 个工程
    • 2.1 创建工程
    • 2.2 配置调试器
    • 2.3 配置 GPIO 操作 LED
  • 三、UART 编程
    • 3.1 使用 STM32CubeMX 进行配置
      • 3.1.1 UART1
      • 3.1.2 配置 RS485方向引脚
    • 3.2 封装 UART
    • 3.3 上机实验
      • 3.3.1 硬件连接
      • 3.3.2 STM32H5 程序改造
      • 3.3.3 STM32F030程序调试
      • 3.3.4 现象
  • 四、libmodbus 移植
    • 4.1 移植 libmodbus
    • 4.2 使用 modbus 控制设备
    • 4.3 上机实验
  • 五、传感器设计
    • 5.1 设计思路
    • 5.2 三款传感器功能及所用引脚
    • 5.3 点表设计
      • 5.3.1 开关量模块(SWITCH)
      • 5.3.2 环境监测模块(ENV_MONITOR)
      • 5.3.3 温湿度模块(TEMP HUMI)
    • 5.4 开关量传感器程序设计
    • 5.5 环境监测传感器程序设计
      • 5.5.1 硬件电路
      • 5.5.2 配置 GPIO 和 ADC
      • 5.5.3 读取 ADC 的关键代码
      • 5.5.4 相关代码及现象
    • 5.6 温湿度传感器程序设计
      • 5.6.1 硬件电路与操作方法
      • 5.6.2 配置 I2C
      • 5.6.3 读取温湿度关键代码
      • 5.6.4 相关代码及现象

一、硬件资源介绍与接线

Modbus 传感器开发套件共有三个, 三个板子的使用的主控方案是 STM32F030芯片,硬件接口资源如下图所示:
开关量模块
在这里插入图片描述

温湿度变送器模块
在这里插入图片描述

环境检测模块
在这里插入图片描述

二、创建与体验第 1 个工程

2.1 创建工程

启动 STM32CubeMX 后,点击如下图标开始选择 MCU:

在这里插入图片描述

如下图输入型号“STM32F030CCT”,双击找到的芯片, 开始创建工程:

在这里插入图片描述

调高 CPU 频率:

在这里插入图片描述

配置工程, 如下操作:

在这里插入图片描述

指定代码生成方法, 如下:

在这里插入图片描述

2.2 配置调试器

新建的工程要配置调试器,参考《2.2.3 配置调试器》 。

然后就可以编译程序、烧写运行了。

2.3 配置 GPIO 操作 LED

根据开发板原理图可以看到 F030的LED 引脚图如下:

在这里插入图片描述

可以双击打开工程中如下文件进入STM32CubeMX进行配置:

在这里插入图片描述

然后如下配置 PB11 、PB12 、PB13 为输出引脚:

在这里插入图片描述

点击右上角的 “GENERATE CODE”按钮后打开工程。

在main函数的循环里, 增加如下代码:

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
    /* set LED output high */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); //LED1
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //LED2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); //LED3
    HAL_Delay(500);
    /* set LED output low */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);//LED1
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); //LED2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); //LED3
    HAL_Delay(500);
}

最后编译、烧写、运行, 可以看到开发板的 LED 闪烁。

三、UART 编程

3.1 使用 STM32CubeMX 进行配置

RS4385接口原理图如下:

在这里插入图片描述

需要在STM32CubeMX里配置UART1,并且配置PA8为输出引脚。
其中RS485 CTRL引脚作用:是用来控制对应的SIT3088ETK芯片,决定了数据的走向:把数据发送出去或者接收到数据后回送给主控。当要发送数据的时候将此引脚设置为高电平(DE),接收数据时设置为低电平(RE#)

3.1.1 UART1

先使能 UART1:

在这里插入图片描述

然后使能中断:

在这里插入图片描述

在前面STM32H5的UART程序里使用了DMA,本节故意不使用DMA而使用纯中断来 实现UART,多学一种编程方法。

3.1.2 配置 RS485方向引脚

STM32H5主控板上使用的RS485转换芯片是MAX13487EESA,它会自动切换发送、 接收方向,无需程序进行方向的控制。使用STM32F030制作的“廉价传感器”里,使用 的RS485转换芯片是SIT3088ETK,它需要使用一个GPIO来控制方向,如下图所示:

在这里插入图片描述

上图中,RS485_CTRL使用的引脚是PA8,所以还需要把它配置为输出引脚,输出低 电平(让SIT3088ETK默认为接收状态) 。如下配置:

在这里插入图片描述

3.2 封装 UART

将以下路径的文件复制并粘贴到上面创建的工程uart_demoo:
在这里插入图片描述
在这里插入图片描述
这里只需要uart_driver,所以将其他文件删除,如下:
在这里插入图片描述
接下来用source insight编写uart_driver.c以及uart_driver.h:
uart_driver.c

#include <stdio.h>
#include <string.h>
#include "uart_device.h"

struct UART_Data {
    UART_HandleTypeDef *huart;
    GPIO_TypeDef* GPIOx_485;	//使用哪一组引脚
    uint16_t GPIO_Pin_485;		//使用哪一个引脚
    QueueHandle_t xRxQueue;
    QueueHandle_t xTxSem;
    uint8_t rxdata;
};

static struct UART_Data g_uart1_data = {
    &huart1,
    GPIOA,
    GPIO_PIN_8,
};

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart == &huart1)
	{
        struct UART_Data * uart_data = g_uart1_dev.priv_data;       //这里只有uart1 所以直接获得其私有数据 
		
		xSemaphoreGiveFromISR(uart_data->xTxSem, NULL);

		/* 配置RS485转换芯片的方向引脚,让它输出0表示接收 */
        HAL_GPIO_WritePin(uart_data->GPIOx_485, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);	//对应PA8
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart == &huart1)
	{
        struct UART_Data * uart_data = g_uart1_dev.priv_data;        
		xQueueSendFromISR(uart_data->xRxQueue, (const void *)&uart_data->rxdata, NULL);
		
        HAL_UART_Receive_IT(uart_data->huart, &uart_data->rxdata, 1);	//若接收完毕则再次启动 为了下一次接收
	}	
	
}

static int stm32_uart_init(struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit)
{
	struct UART_Data * uart_data = pDev->priv_data;

	if (!uart_data->xRxQueue)											//如果队列为空 则创建队列和信号量
	{
		uart_data->xRxQueue = xQueueCreate(200, 1);
		uart_data->xTxSem	= xSemaphoreCreateBinary( );
		
        /* 配置RS485转换芯片的方向引脚,让它输出0表示接收 */
        HAL_GPIO_WritePin(uart_data->GPIOx_485, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);	
		
		HAL_UART_Receive_IT(uart_data->huart, &uart_data->rxdata, 1);	//启动数据传输
	}
	return 0;
}

static int stm32_uart_send(struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout)
{
	struct UART_Data *uart_data = pDev->priv_data;

    /* 配置RS485转换芯片的方向引脚,让它输出1表示发送 */
    HAL_GPIO_WritePin(uart_data->GPIOx_485, uart_data->GPIO_Pin_485, GPIO_PIN_SET);

	HAL_UART_Transmit_IT(uart_data->huart, datas, len);

	/* 等待1个信号量(为何不用mutex? 因为在中断里Give mutex会出错) */
	if (pdTRUE == xSemaphoreTake(uart_data->xTxSem, timeout))
		HAL_GPIO_WritePin(uart_data->GPIOx_485, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);
		return 0;
	else
		HAL_GPIO_WritePin(uart_data->GPIOx_485, uart_data->GPIO_Pin_485, GPIO_PIN_RESET);
		return -1;
}


static int stm32_uart_recv(struct UART_Device *pDev, uint8_t *pData, int timeout)
{
	struct UART_Data *uart_data = pDev->priv_data;

	if (pdPASS == xQueueReceive(uart_data->xRxQueue, pData, timeout))
		return 0;
	else
		return -1;
}

static int stm32_uart_flush(struct UART_Device *pDev)
{
	struct UART_Data *uart_data = pDev->priv_data;

	int cnt = 0;
	uint8_t data;
	while (1)
	{
		if (pdPASS != xQueueReceive(uart_data->xRxQueue, &data, 0))
			break;
		cnt++;
	}
	return cnt;
}

struct UART_Device g_uart1_dev = {"uart1", stm32_uart_init, stm32_uart_send, stm32_uart_recv, stm32_uart_flush, &g_uart1_data};

static struct UART_Device *g_uart_devices[] = {&g_uart1_dev};


struct UART_Device *GetUARTDevice(char *name)
{
	int i = 0;
	for (i = 0; i < sizeof(g_uart_devices)/sizeof(g_uart_devices[0]); i++)
	{
		if (!strcmp(name, g_uart_devices[i]->name))
			return g_uart_devices[i];
	}
	
	return NULL;
}

uart_driver.h

#ifndef __UART_DEVICE_H
#define __UART_DEVICE_H

#include <stdint.h>

struct UART_Device {
    char *name;
	int (*Init)( struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit);
	int (*Send)( struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout);
	int (*RecvByte)( struct UART_Device *pDev, uint8_t *data, int timeout);
	int (*Flush)(struct UART_Device *pDev);
	void *priv_data;
};

struct UART_Device *GetUARTDevice(char *name);
    
#endif /* __UART_DEVICE_H */

3.3 上机实验

要测试 STM32F030 的串口, 只需要把它的 485 接口连接到 PC 去就可以了,但是我们没 有 PC 上使用的“USB 转 485”模块,所以使用 STM32H5 来实现一个“USB 转 485 模块”:

  • 它从 USB 串口读到数据,再从 485 接口发送出去;

  • 它从 485 接口读到数据,再从 USB 串口发送给 PC。

3.3.1 硬件连接

在这里插入图片描述

3.3.2 STM32H5 程序改造

本节源码为“h5_demo”。
app_freertos.c

static void RS485toUSB( void *pvParameters )	
{
    struct UART_Device *pUSBUART = GetUARTDevice("usb");
    struct UART_Device *pRS485UART = GetUARTDevice("uart4");
    uint8_t data;

    while (1)
    {
        if (0 == pRS485UART->RecvByte(pRS485UART, &data, portMAX_DELAY))
        {
            pUSBUART->Send(pUSBUART, &data, 1, 100);
        }
    }
}


static void USBtoRS485( void *pvParameters )	
{
    struct UART_Device *pUSBUART = GetUARTDevice("usb");
    struct UART_Device *pRS485UART = GetUARTDevice("uart4");
    uint8_t data;

    while (1)
    {
        if (0 == pUSBUART->RecvByte(pUSBUART, &data, portMAX_DELAY))
        {
            pRS485UART->Send(pRS485UART, &data, 1, 100);
        }
    }
}

struct UART_Device *pUSBUART = GetUARTDevice("usb");
struct UART_Device *pRS485UART = GetUARTDevice("uart4");

pUSBUART->Init(pUSBUART, 115200, 'N', 8, 1);		//初始化:设备ID、波特率、检验位、数据位、停止位
pRS485UART->Init(pRS485UART, 115200, 'N', 8, 1);

3.3.3 STM32F030程序调试

本节源码为“f030_demo”。
freertos.c

#include "uart_device.h"

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  struct UART_Device *pdev = GetUARTDevice("uart1");
  pdev->Init(pdev, 115200, 'N', 8, 1);

  uint8_t data;
  int levels[3] = {0};
  
  while (1)
  {
      if (0 == pdev->RecvByte(pdev, &data, portMAX_DELAY))	//读取到来自h5的数据
      {	
          pdev->Send(pdev, &data, 1, 100);					//将其回送给h5
          
          switch (data)
          {
            case 1:
            {
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, levels[0]); //LED1
                levels[0] = !levels[0];						//每次发送完毕取反 下次发送就会使得现象与上次相反 即led熄灭或亮
                break;
            }
            case 2:
            {
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, levels[1]); //LED1
                levels[1] = !levels[1];
                break;
            }
            case 3:
            {
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, levels[2]); //LED1
                levels[2] = !levels[2];
                break;
            }
          }
      }
  }
  
  /* USER CODE END StartDefaultTask */
}

3.3.4 现象

刚烧录好时三个LED全亮,如下:
在这里插入图片描述
分别输入01、02、03去控制LED熄灭(第一次输入开始赋值,取反后熄灭):
在这里插入图片描述

四、libmodbus 移植

本节源码为“E:\QuanCJ\libmodbus_to_f030\f030_demo”。

4.1 移植 libmodbus

把“7-6_STM32F030串口测试\h5_demo\demo\Middlewares\Third_Party\libmodbus”整 个目录复制到f030_demo中。
在这里插入图片描述

在Keil工程中添加代码, 如下:

在这里插入图片描述

4.2 使用 modbus 控制设备

STM32F030作为从设备(sever),编写“Core\Src\freertos.c”:

#include "uart_device.h"
#include "modbus.h"
#include "errno.h"
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
	
	uint8_t *query;
	modbus_t *ctx;
	int rc;
	modbus_mapping_t *mb_mapping;
	
	ctx = modbus_new_st_rtu("uart1", 115200, 'N', 8, 1);
	modbus_set_slave(ctx, 1);
	query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH);

	mb_mapping = modbus_mapping_new_start_address(0,
												  10,
												  0,
												  10,
												  0,
												  10,
												  0,
												  10);
	
	memset(mb_mapping->tab_bits, 0, mb_mapping->nb_bits);
	memset(mb_mapping->tab_registers, 0x55, mb_mapping->nb_registers*2);

	rc = modbus_connect(ctx);//连接
	if (rc == -1) {
		//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		vTaskDelete(NULL);;
	}

	for (;;) {
		do {
			rc = modbus_receive(ctx, query);//接收主机发送过来的modbus请求
			/* Filtered queries return 0 */
		} while (rc == 0);
 
		/* The connection is not closed on errors which require on reply such as
		   bad CRC in RTU. */
		if (rc == -1 && errno != EMBBADCRC) {
			/* Quit */
			continue;
		}

		rc = modbus_reply(ctx, query, rc, mb_mapping);//回复主机
		if (rc == -1) {
			//break;
		}

		if (mb_mapping->tab_bits[0])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); //LED1:可读可写的第0位为1则led1亮
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);
		if (mb_mapping->tab_bits[1])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); //LED2:可读可写的第1位为1则led2亮
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
		if (mb_mapping->tab_bits[2])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); //LED3:可读可写的第2位为1则led3亮
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);		
	}

	modbus_mapping_free(mb_mapping);
	vPortFree(query);
	/* For RTU */
	modbus_close(ctx);
	modbus_free(ctx);

	vTaskDelete(NULL);	
  /* USER CODE END StartDefaultTask */
}

4.3 上机实验

在这里插入图片描述
在这里插入图片描述
这里分别三次输入0、1、2并设置成on,则三个LED都亮。

五、传感器设计

5.1 设计思路

上位机(PC 软件)或中控(STM32H5)通过 modbus 协议访问 STM32F030 传感器时,读写的是 STM32F030 分配出来的 4 个类型的缓冲区。这里需要解决 2 个问题:

  • 这 4 个类型的缓冲区起始地址、大小分别是多少? 这根据传感器的功能来设置。比如有 2 个按键,那么就可以分配 2 个“只读的位寄存器”(DI)。

  • 这些寄存器的值, 如何跟硬件对应? 比如上位机读 DI 寄存器时, 谁提供这些值?传感器的程序应该读取按键值,填充 DI 寄存器。

第 1 个步骤,被称为“点表设计”。

在这里插入图片描述

在这里插入图片描述

①、主设备是主控,从设备就是传感器,我们在设置这些传感器时,首先需要确定其设备地址,因为每个传感器的设备地址不同

②、我们使用 libmodbus 来传输数据时,对离散输入状态会分配一个 buf ,对线圈状态也会分配一个 buf ,对下面的多位值寄存器输入寄存器和保持寄存器也会分配一个 buf ,这个 buf 会包含起始地址(指定四个寄存器其中一个的寄存器起始地址)、个数和功能。个数需要根据硬件功能来确定;起始地址由自己决定,但一般都是从0开始;这些寄存器的功能是什么,换句话说我们读取到寄存器的值有什么含义,他的值来自哪里,所以功能分为两部分:值的含义、读取数值的来源以及写入数值如何操作硬件,我们可以以开关量寄存器为例,当我们读 DI 寄存器时,确实可以读取到数据,读取到 1 或 0 表示什么意思,此时就需要我们来决定,比如读取到 1 表示按键被按下。拿前面的几个传感器做解释如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.2 三款传感器功能及所用引脚

这 3 款传感器的原理图在网盘如下目录里:

在这里插入图片描述

它们控制外设所用的引脚,列表如下:

功能使用的引脚描述寄存器类别
ENV_MONITOR 环境监测模块BEEP1PB15高电平发声DO
BEEP2PB14高电平发声DO
LED1PB11低电平发光DO
LED2PB12低电平发光DO
LED3PB13低电平发光DO
OPTO_ADCPA1电压值跟光强成反比AI
RES_ADCPA2电压值跟可调电阻成反比AI
SWITCH开关量模块KEY1PA3低电平表示被按下DI
KEY2PA4低电平表示被按下DI
KEY3PA5低电平表示被按下DI
K1_CTRLPB5高电平使能继电器DO
K2_CTRLPB4高电平使能继电器DO
LED1PB11低电平发光DO
LED2PB12低电平发光DO
LED3PB13低电平发光DO
TEMP_HUMI温湿度模块BEEP1PB15高电平发声DO
BEEP2PB14高电平发声DO
LED1PB11低电平发光DO
LED2PB12低电平发光DO
LED3PB13低电平发光DO
I2C1_SCLPB6可以读到温度、湿度 AIAI
I2C2_SDAPB7可以读到温度、湿度 AIAI

5.3 点表设计

所谓点表, 就是一个 modbus 设备,它的地址是什么? 它里面 4 类寄存器的地址、功能是什么

在查看点表时,经常碰到“遥测、遥信、遥控、遥调”的概念。它们实质上就是前面讲解 modbus 时引入的“AI、DI、DO、AO”。这些概念起源于电力系统。

电力领域中四遥系统是指遥测、遥信、遥控、遥调功能系统,四遥功能是电力监控系统最基本最重要的功能。具体来说:

  • 遥测(遥测信息,AI):远程测量;远方测量显示诸如电流、电压、功率、压力、温度等模拟量;

  • 遥信(遥信信息,DI):远程信号;远方监视各类电气开关和设备、机械设备的工作状态和运转情况状态等;

  • 遥控(遥控信息,DO):远程控制;接受并执行遥控命令,远方控制或保护电气设备及电气机械化的分合起停等工作状态;

  • 遥调(遥调信息,AO):远程调节;接受并执行遥调命令,远方设定及调整所控设备的工作参数、标准参数;四遥遥测、遥信、遥控、遥调常常被简称为 AI、DI、DO、AO。

AI、DI、DO、AO 都是英文名称的首字母缩写,A 的英文全称 Analog (模拟量)、D 的英文全称 Digital (数字量) 、I 的英文全称 Input (输入)、O 的英文全称 Output (输出)。 因此,AI 表示的是模拟信号输出,AO 是模拟信号输入,DI 是数字信号输入,DO 是数字信号输出

随着技术不断地进步,现在也有五遥的说法, 即在四遥的基础上加上遥视, 遥视指的是指利用包括电子技术、计算机技术、自动化技术等监视并记录设备运行情况和环境安全 情况。因此伴随着技术发展,电力系统中从一遥(遥信 DI)阶段、发展到二遥(遥信 AI、 遥测 DI)、三遥(遥信 AI、遥测 DI 和遥控 DO)、四遥(遥信 AI、遥测 DI、遥控 DO 和遥调 AO);现在开始四遥向五遥过渡。

原文链接:https://blog.csdn.net/LuohenYJ/article/details/106027626

在阅读点表时,还会碰到下表中的“PLC/组态地址”,或者表中的简称“0x、1x、4x、 3x”,它们的本质都是用来分辨“AI、AO、DI、DO”四类寄存器(需要注意:在 PLC 地址中,第一位是用来分辨寄存器,而后面的才是真正的地址,都是从 1 开始,而在 modbus 中,寄存器地址从 0 开始,这里有个转换关系):

寄存器种类PLC/组态地址Modbus 寄存器地址范围简称读写状态
线圈状态00001~099990000H~FFFFH0x可读可写
离散输入状态10001~199990000H~FFFFH1x只读
保持寄存器40001~499990000H~FFFFH4x可读可写
输入寄存器30001~399990000H~FFFFH3x只读

点表的设计,是完全由开发人员自行定义的。

5.3.1 开关量模块(SWITCH)

寄存器说明:

在这里插入图片描述

5.3.2 环境监测模块(ENV_MONITOR)

寄存器说明:

在这里插入图片描述

5.3.3 温湿度模块(TEMP HUMI)

寄存器说明:
在这里插入图片描述

5.4 开关量传感器程序设计

继电器原理图如下:

在这里插入图片描述

继电器对外的信号有 3 个:

  • COM:公共端,通常是中间的触点, 与常开或常闭触点相连
  • NC(Normally Closed): 常闭接口,继电器吸合前与 COM 连接, 吸合后悬空
  • NO(Normally Open): 常开接口, 继电器吸合前悬空, 吸合后与 COM 连接

开路即通路、断路,闭合指的是开关闭合,也就是说,在没有任何上电之类的动作时,NC 和 COM 端相当于已经连通。

本节源码为“ E:\QuanCJ\switch_project\f030_demo”。

/* 这里用宏来替代是为了方便后面的其他传感器 */
#include "uart_device.h"
#include "modbus.h"
#include "errno.h"

#define USE_SWITCH_SENSOR 1			//1表示这里使用的是开关量传感器 则在ifdef中的定义及相关代码都会生效
#ifdef USE_SWITCH_SENSOR 

#define SLAVE_ADDR 					1		//开关量传感器的地址是01H
#define NB_BITS							5		//可读可写的位寄存器(DO)有5个
#define NB_INPUT_BITS				3		//只读的位寄存器(DI)有3个
#define NB_REGISTERS				0		//可读可写的多位寄存器(DO)有0个
#define NB_INPUT_REGISTERS	0		//只读的多位寄存器(AI)有0个

#endif

void StartDefaultTask(void *argument)
{
		/* USER CODE BEGIN StartDefaultTask */
		/* Infinite loop */
		
		uint8_t *query;
		modbus_t *ctx;
		int rc;
		modbus_mapping_t *mb_mapping;
		GPIO_PinState val;
		
		ctx = modbus_new_st_rtu("uart1", 115200, 'N', 8, 1);
		modbus_set_slave(ctx, SLAVE_ADDR);	//第二个参数是设备地址 用上面定义的宏表示开关量传感器的地址
		query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH);

		mb_mapping = modbus_mapping_new_start_address(0,									//分别是四类寄存器的起始地址和个数 起始地址一般为0 个数通过我们电表设计并将宏定义在前面
																									NB_BITS,
																									0,
																									NB_INPUT_BITS,
																									0,
																									NB_REGISTERS,
																									0,
																									NB_INPUT_REGISTERS);
		
		rc = modbus_connect(ctx);
		if (rc == -1) {
			//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
			modbus_free(ctx);
			vTaskDelete(NULL);;
		}

		for (;;) {
			do {
				rc = modbus_receive(ctx, query);
				/* Filtered queries return 0 */
			} while (rc == 0);
 
		/* The connection is not closed on errors which require on reply such as
		   bad CRC in RTU. */
		if (rc == -1 && errno != EMBBADCRC) {
			/* Quit */
			continue;
		}
		
		/* 更新寄存器的值 
		 * 1.读取GPIO
		 * 2.更新寄存器
		 */
		
#ifdef USE_SWITCH_SENSOR
		/* KEY1 */
		val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3);		//读取按键1的值
		if (val == GPIO_PIN_RESET)										//如果按键1被按下
		{
			mb_mapping->tab_input_bits[0] = 1;					//设置按键1对应DI寄存器的值 1表示被按下 0表示未被按下
		}
		else
		{
			mb_mapping->tab_input_bits[0] = 0;
		}
		
		/* KEY2 */
		val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4);		//读取按键2的值
		if (val == GPIO_PIN_RESET)										//如果按键2被按下
		{
			mb_mapping->tab_input_bits[1] = 1;					//设置按键2对应DI寄存器的值 1表示被按下 0表示未被按下
		}
		else
		{
			mb_mapping->tab_input_bits[1] = 0;
		}
		
		/* KEY3 */
		val = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5);		//读取按键3的值
		if (val == GPIO_PIN_RESET)										//如果按键3被按下
		{
			mb_mapping->tab_input_bits[2] = 1;					//设置按键3对应DI寄存器的值 1表示被按下 0表示未被按下
		}
		else
		{
			mb_mapping->tab_input_bits[2] = 0;
		}	
#endif

		rc = modbus_reply(ctx, query, rc, mb_mapping);	//将寄存器的数值返回 所以前面部分代码是对寄存器进行更新
		if (rc == -1) {
			//break;
		}

#ifdef USE_SWITCH_SENSOR
		/* stitch1 */
		if (mb_mapping->tab_bits[0])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); 		//stitch1(高电平使能继电器)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
		
		/* stitch2 */
		if (mb_mapping->tab_bits[1])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET); 		//stitch2(高电平使能继电器)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET);
		
		/* LED1 */
		if (mb_mapping->tab_bits[2])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);

		/* LED2 */
		if (mb_mapping->tab_bits[3])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);	

		/* LED3 */
		if (mb_mapping->tab_bits[4])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); 	//LED3(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);	
#endif  		
	}

	modbus_mapping_free(mb_mapping);
	vPortFree(query);
	/* For RTU */
	modbus_close(ctx);
	modbus_free(ctx);

	vTaskDelete(NULL);	
  /* USER CODE END StartDefaultTask */
}

现象:打开 Modbus Poll 连接上H5开发板作 usb 串行设备,并进行相应配置,将1或0输入LED和继电器对应的寄存器,会发现对应的灯发生亮灭,同时按下任意按键发现寄存器值发生变化:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
两个继电器及三个LED:
在这里插入图片描述
在这里插入图片描述
三个按键:
在这里插入图片描述
在这里插入图片描述

5.5 环境监测传感器程序设计

5.5.1 硬件电路

光敏电路如下, 光照越强,U6 阻值越低,OPTO_ADC 电压值就越低:

在这里插入图片描述

可调电阻器如下,R33 阻值越大,RES_ADC 电压值越小:

在这里插入图片描述

5.5.2 配置 GPIO 和 ADC

先在STM32CubmeMX里配置GPIO和ADC 引脚,使能“Discontinuous Conversion Mode ”:

在这里插入图片描述

5.5.3 读取 ADC 的关键代码

// 1. 检验
HAL_ADCEx_Calibration_Start(&hadc) ;

// 启动、读2次数值
for (int i = 0; i < 2; i++)
{
    HAL_ADC_Start(&hadc);
    if (HAL_OK == HAL_ADC_PollForConversion(&hadc, 100))
    {
    	mb_mapping->tab_input_registers[i] = HAL_ADC_GetValue(&hadc); 
    }
}

5.5.4 相关代码及现象

本节源码为“E:\QuanCJ\environment_project\f030_demo”。

#include "uart_device.h"
#include "modbus.h"
#include "errno.h"

#define USE_ENV_MONITOR_SENSOR 1

#ifdef USE_ENV_MONITOR_SENSOR		//如果使用环境检测传感器
extern ADC_HandleTypeDef hadc;
#define SLAVE_ADDR 					2		//环境检测传感器的地址是02H
#define NB_BITS							5		//可读可写的位寄存器(DO)有5个
#define NB_INPUT_BITS				0		//只读的位寄存器(DI)有0个
#define NB_REGISTERS				0		//可读可写的多位寄存器(DO)有0个
#define NB_INPUT_REGISTERS	2		//只读的多位寄存器(AI)有2个

#endif

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
		/* Infinite loop */
		
		uint8_t *query;
		modbus_t *ctx;
		int rc;
		modbus_mapping_t *mb_mapping;
		GPIO_PinState val;

#ifdef USE_ENV_MONITOR_SENSOR					//如果使用环境检测传感器
		HAL_ADCEx_Calibration_Start(&hadc);		//进行ADC校验
#endif
	
		ctx = modbus_new_st_rtu("uart1", 115200, 'N', 8, 1);
		modbus_set_slave(ctx, SLAVE_ADDR);	//第二个参数是设备地址 用上面定义的宏表示开关量传感器的地址
		query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH);

		mb_mapping = modbus_mapping_new_start_address(0,									//分别是四类寄存器的起始地址和个数 起始地址一般为0 个数通过我们电表设计并将宏定义在前面
																									NB_BITS,
																									0,
																									NB_INPUT_BITS,
																									0,
																									NB_REGISTERS,
																									0,
																									NB_INPUT_REGISTERS);
		
		rc = modbus_connect(ctx);
		if (rc == -1) {
			//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
			modbus_free(ctx);
			vTaskDelete(NULL);;
		}

		for (;;) {
			do {
				rc = modbus_receive(ctx, query);
				/* Filtered queries return 0 */
			} while (rc == 0);
 
		/* The connection is not closed on errors which require on reply such as
		   bad CRC in RTU. */
		if (rc == -1 && errno != EMBBADCRC) {
			/* Quit */
			continue;
		}
		
		/* 更新寄存器的值(开关量传感器) 
		 * 1.读取GPIO
		 * 2.更新寄存器
		 */

#ifdef USE_ENV_MONITOR_SENSOR												//如果使用环境检测传感器

		/* 读取ADC值并更新到对应的多位寄存器 */
		for (int i = 0; i < 2; i++)
		{
			HAL_ADC_Start(&hadc);
			if (HAL_OK == HAL_ADC_PollForConversion(&hadc, 100))
			{
				mb_mapping->tab_input_registers[i] = HAL_ADC_GetValue(&hadc);		//得到ADC的值赋给对应的多位AI寄存器
			}
		}

#endif
		
		rc = modbus_reply(ctx, query, rc, mb_mapping);	//将寄存器的数值返回 所以前面部分代码是对寄存器进行更新
		if (rc == -1) {
			//break;
		}

#ifdef USE_ENV_MONITOR_SENSOR																	//如果使用环境监测量传感器
		/* beep1 */
		if (mb_mapping->tab_bits[0])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); 		//beep1(高电平发声)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
		
		/* beep2 */
		if (mb_mapping->tab_bits[1])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); 		//beep2(高电平发声)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
		
		/* LED1 */
		if (mb_mapping->tab_bits[2])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);

		/* LED2 */
		if (mb_mapping->tab_bits[3])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);	

		/* LED3 */
		if (mb_mapping->tab_bits[4])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); 	//LED3(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);	
#endif
	}

	modbus_mapping_free(mb_mapping);
	vPortFree(query);
	/* For RTU */
	modbus_close(ctx);
	modbus_free(ctx);

	vTaskDelete(NULL);	
  /* USER CODE END StartDefaultTask */
}

现象:打开 Modbus Poll 连接上H5开发板作 usb 串行设备,并进行相应配置,将1或0输入LED和蜂鸣器对应的寄存器,会发现对应的灯发生亮灭或者蜂鸣器响,同时光敏电阻和可调电阻对应寄存器值发生变化:
两个蜂鸣器和三个LED:
在这里插入图片描述
在这里插入图片描述
光敏电阻和可调电阻:
在这里插入图片描述
在这里插入图片描述

5.6 温湿度传感器程序设计

本节源码为“ E:\QuanCJ\TEMP_HUMI_project\f030_demo”。

5.6.1 硬件电路与操作方法

原理图如下:

在这里插入图片描述

AHT20 芯片资料在网盘如下目录:

在这里插入图片描述

AHT20 操作方法如下:

在这里插入图片描述
在这里插入图片描述

详解如下:

  • 发送测量命令:传感器的 VDD 上电后需等待 5ms发送写测量命令 0x70 0xAC 0x330x00, 等待 80ms 测量完成;

  • 获取温湿度校准数据: 在等待 80ms 测量完成后, 发送 0x71 读传感器,可获取状态字 Status、温湿度校准数据 SRH[19:0]、ST[19:0]以及校准字 CRC;(检查数据正确性:算出在 0x71 和 CRC 中间的 6 组数据的校验码,与得到的 CRC 进行对比,一致说明数据无误)

  • 根据公式计算温湿度:

在这里插入图片描述

计算检验码的函数如下:

//**********************************************************//
//CRC校验类型:  CRC8
//多项式:  X8+X5+X4+1
//Poly:0011 0001 0x31
unsigned char Calc_CRC8(unsigned char *message,unsigned char Num)
{
    unsigned char i;
    unsigned char byte;
    unsigned char crc =0xFF;
    for (byte = 0;byte<Num;byte++)
    {
        crc^=(message[byte]);
        for(i=8;i>0;--i)
        {
            if(crc&0x80)
            crc=(crc<<1)^0x31;
            else
            crc=(crc<<1);
        }
    }
    return crc;
}//
**********************************************************//

5.6.2 配置 I2C

在这里插入图片描述

5.6.3 读取温湿度关键代码

·
读取一次温湿度值, 耗时至少 80ms。不可能在接收到modbus 请求后再去读温湿度。而是使用另一个任务不断读取温湿度

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) ;

HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) ;

5.6.4 相关代码及现象

本节源码为“E:\QuanCJ\TEMP_HUMI_project\f030_demo”。

#include "uart_device.h"
#include "modbus.h"
#include "errno.h"

#define USE_TEMP_HUMI_SENSOR 1

#ifdef USE_TEMP_HUMI_SENSOR 				//如果使用温湿度传感器
extern void AHT20Task(void *argument);
static void art20_get_datas(uint16_t *temp, uint16_t *humi);
#define SLAVE_ADDR 					3		//温湿度传感器的地址是03H
#define NB_BITS							5		//可读可写的位寄存器(DO)有5个
#define NB_INPUT_BITS				0		//只读的位寄存器(DI)有0个
#define NB_REGISTERS				0		//可读可写的多位寄存器(DO)有0个
#define NB_INPUT_REGISTERS	2		//只读的多位寄存器(AI)有2个
#endif

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
#ifdef USE_TEMP_HUMI_SENSOR	
	osThreadNew(AHT20Task, NULL, &defaultTask_attributes);
#endif

/* USER CODE BEGIN Header_StartDefaultTask */

#ifdef USE_TEMP_HUMI_SENSOR
//**********************************************************//
//CRC校验类型:  CRC8
//多项式:  X8+X5+X4+1
//Poly:0011 0001 0x31
unsigned char Calc_CRC8(unsigned char *message,unsigned char Num)
{
    unsigned char i;
    unsigned char byte;
    unsigned char crc =0xFF;
    for (byte = 0;byte<Num;byte++)
    {
        crc^=(message[byte]);
        for(i=8;i>0;--i)
        {
            if(crc&0x80)
            crc=(crc<<1)^0x31;
            else
            crc=(crc<<1);
        }
    }
    return crc;
}//

static uint32_t g_temp, g_humi;//温度湿度都是20位数据

static void art20_get_datas(uint16_t *temp, uint16_t *humi)
{
		*temp = g_temp;
		*humi = g_humi;
}

void AHT20Task(void *argument)
{
		uint8_t cmd[] = {0xAC, 0x33, 0x00};		//测试指令(这里需要注意:0x70也是测试指令 但该指令也是I2C要发送的起始设备地址 所以直接将0x70做起始设备地址并发送即可)
		uint8_t datas[7];
		uint8_t crc;
		
		extern I2C_HandleTypeDef hi2c1;
		
		vTaskDelay(5);		//上电后等待5ms
		
		while (1)
		{
			if (HAL_OK == HAL_I2C_Master_Transmit(&hi2c1, 0x70, cmd, 3, 100))
			{
				vTaskDelay(80);		//发送完测试指令延时80ms
				if (HAL_OK == HAL_I2C_Master_Receive(&hi2c1, 0x70, datas, 7, 100))	//前面已经发送测试指令 所以发送成功会以0x70为设备地址继续发送0x71 所以这里第二个参数只要是0x70就可以接收到后续的数据及校验码
				{
					/* 计算校验码 */
					crc = Calc_CRC8(datas, 6);		//datas数组中的前6个数组生成校验码与原来的校验码进行对比
					if (crc == datas[6])					//校验码一致 数据准确
					{
						/* 取出湿度温度数据 */
						g_humi = ((uint32_t)datas[1] << 12) | ((uint32_t)datas[2] << 4) | ((uint32_t)datas[3] >> 4);
						//需要注意数据的定义 datas是8位 而g_humi是32位 要想取到正确的20位数据 需要将数据强转成32位数据并进行移位 原先的8位数据强转32位后数据只有最后的8位数据有效 其他位全为0 所以只需要将得到的数据按照20位依次位移即可 根据数据表格 湿度从data[1]开始 所以取出data[1]左移12位 则右边有12位有效数据 再取出data[2]左移4位 则右边有4位有效数据 最后data[3]中前一半数据为湿度 所以取出data[3]右移4位 相当于获得data[3]的前四位数据 刚好补上4位有效数据 这样子加起来就是20位数据
						
						g_temp = (((uint32_t)datas[3] & 0x0f) << 16) | ((uint32_t)datas[4] << 8) | ((uint32_t)datas[5]);
						//这里的取值也与湿度类似 data[3]后半部分是温度数据 所以第一部分计算的含义data[3]的后4位有效 然后就依次左移 直到取出完整的20位数据

						//这样计算是因为计算出来的数值有小数 而我们定义的数值是整数 所以直接将单位缩小10倍 数值增大10倍
						g_humi = g_humi * 100 * 10/ 0x100000; /* 0.1% */
						g_temp = g_temp * 200 * 10/ 0x100000 - 500; /* 0.1C */						
					}
				}
			}
			vTaskDelay(20);		 //每隔20ms获取一次数据
		}
}
#endif

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
		/* Infinite loop */
		
		uint8_t *query;
		modbus_t *ctx;
		int rc;
		modbus_mapping_t *mb_mapping;
		GPIO_PinState val;

#ifdef USE_ENV_MONITOR_SENSOR							//如果使用环境检测传感器
		HAL_ADCEx_Calibration_Start(&hadc);		//进行ADC校验
#endif
	
		ctx = modbus_new_st_rtu("uart1", 115200, 'N', 8, 1);
		modbus_set_slave(ctx, SLAVE_ADDR);	//第二个参数是设备地址 用上面定义的宏表示开关量传感器的地址
		query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH);

		mb_mapping = modbus_mapping_new_start_address(0,									//分别是四类寄存器的起始地址和个数 起始地址一般为0 个数通过我们电表设计并将宏定义在前面
																									NB_BITS,
																									0,
																									NB_INPUT_BITS,
																									0,
																									NB_REGISTERS,
																									0,
																									NB_INPUT_REGISTERS);
		
		rc = modbus_connect(ctx);
		if (rc == -1) {
			//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
			modbus_free(ctx);
			vTaskDelete(NULL);;
		}

		for (;;) {
			do {
				rc = modbus_receive(ctx, query);
				/* Filtered queries return 0 */
			} while (rc == 0);
 
		/* The connection is not closed on errors which require on reply such as
		   bad CRC in RTU. */
		if (rc == -1 && errno != EMBBADCRC) {
			/* Quit */
			continue;
		}
		
#ifdef USE_TEMP_HUMI_SENSOR									//如果使用温湿度传感器
		/* 这里不能让aht20发起I2C操作去捕捉数据,因为80ms太长了 */
		uint16_t temp,humi;
		art20_get_datas(&temp, &humi);						//用此函数获得温湿度数据
		mb_mapping->tab_input_registers[0] = temp;			//将温度数据上传到对应的传感器
		mb_mapping->tab_input_registers[1] = humi;			//将湿度数据上传到对应的传感器
		
#endif
		
		rc = modbus_reply(ctx, query, rc, mb_mapping);	//将寄存器的数值返回 所以前面部分代码是对寄存器进行更新
		if (rc == -1) {
			//break;
		}

#ifdef USE_TEMP_HUMI_SENSOR												//如果使用温湿度传感器

		/* beep1 */
		if (mb_mapping->tab_bits[0])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); 		//beep1(高电平发声)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
		
		/* beep2 */
		if (mb_mapping->tab_bits[1])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); 		//beep2(高电平发声)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
		
		/* LED1 */
		if (mb_mapping->tab_bits[2])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);

		/* LED2 */
		if (mb_mapping->tab_bits[3])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); 	//LED1(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);	

		/* LED3 */
		if (mb_mapping->tab_bits[4])
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); 	//LED3(低电平发光)
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);	

#endif

	}

	modbus_mapping_free(mb_mapping);
	vPortFree(query);
	/* For RTU */
	modbus_close(ctx);
	modbus_free(ctx);

	vTaskDelete(NULL);	
  /* USER CODE END StartDefaultTask */
}

现象:打开 Modbus Poll 连接上H5开发板作 usb 串行设备,并进行相应配置,将1或0输入LED和蜂鸣器对应的寄存器,会发现对应的灯发生亮灭或者蜂鸣器响,同时温度和湿度对应寄存器值发生变化:
两个蜂鸣器和三个LED:
在这里插入图片描述
在这里插入图片描述
温度和湿度:
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何实现一套键盘鼠标控制两台计算机(罗技Options+ Flow功能快速实现演示)

需求背景 之前我写过一篇文章如何实现一套键盘鼠标控制两台计算机&#xff08;Mouse Without Borders快速上手教程&#xff09;_一套键鼠控制两台电脑-CSDN博客 当我们在局域网内有两台计算机&#xff0c;想使用一套键鼠操控时&#xff0c;可以安装Mouse Without Borders软件…

MacOS 配置github密钥

MacOS 配置github密钥 1. 生成GitHub的SSH密钥对 ssh-keygen -t ed25519 -C "xxxxxxx.com" -f ~/.ssh/id_ed25519_github 其中 xxxxxxxxxxx.com 是注册github、gitee和gitlab的绑定账号的邮箱 -t ed25519:生成密钥的算法为ed25519&#xff08;ed25519比rsa速度快&…

嵌入式Linux之wifi配网脚本分析

嵌入式Linux系统,一般都支持wifi联网,可以通过sh脚本或其它语言代码编程来实现wifi联网。 本篇来介绍一种通过sh脚本来配置wifi的脚本执行原理。 1 sh脚本wifi联网介绍 这里以飞凌开发板中的wifi启动脚本为例来介绍。 在飞凌开发板的串口中,执行如下命令(调用fltest_wif…

Cursor安装与使用,5分钟完成需求

Cursor简单介绍 Cursor是一款基于AI的代码编辑器&#xff0c;旨在帮助开发者更高效地编写和管理代码。它提供了智能代码补全、AI对话和跨文件编辑等创新功能。 一、安装下载 1、下载cursor&#xff1a;https://www.cursor.com/ 2、注册账号&#xff0c;直接拿自己的邮箱登录…

Ubuntu 20.04 Server版连接Wifi

前言 有时候没有网线口插网线或者摆放电脑位置不够时&#xff0c;需要用Wifi联网。以下记录Wifi联网过程。 环境&#xff1a;Ubuntu 20.04 Server版&#xff0c;无UI界面 以下操作均为root用户&#xff0c;如果是普通用户&#xff0c;请切换到root用户&#xff0c;或者在需要权…

IDEA 2024 配置Maven

Step 1:确定下载Apache Maven版本 在IDEA 2024中&#xff0c;随便新建一个Maven项目&#xff1b; 在File下拉菜单栏中&#xff0c;找到Setings&#xff1b; 在Build&#xff0c;Execution&#xff0c;Deployment中找到Maven 确定下载的Apache Maven版本应略低于或等于IDEA绑…

困扰解决:mfc140u.dll丢失的解决方法,多种有效解决方法全解析

当电脑提示“mfc140u.dll丢失”时&#xff0c;这可能会导致某些程序无法正常运行&#xff0c;给用户带来不便。不过&#xff0c;有多种方法可以尝试解决这个问题。这篇文章将以“mfc140u.dll丢失的解决方法”为主题&#xff0c;教大家有效解决mfc140u.dll丢失。 判断是否是“mf…

《只狼》运行时提示“mfc140u.dll文件缺失”是什么原因?“找不到mfc140u.dll文件”要怎么解决?教你几招轻松搞定

《只狼》运行时提示“mfc140u.dll文件缺失”的科普与解决方案 作为一名软件开发从业者&#xff0c;在游戏开发和维护过程中&#xff0c;我们经常会遇到各种运行时错误和系统报错。今天&#xff0c;我们就来探讨一下《只狼》这款游戏在运行时提示“mfc140u.dll文件缺失”的原因…

C++STL容器vector迭代器相关函数

目录 前言 主要参考 vector::begin vector::end vector::rbegin vector::rend vector::cbegin等常量迭代器相关. 共勉 前言 本文将讨论STL容器vector中与迭代器相关的函数&#xff0c;模板参数T为int类型。 主要参考 cpluscplus.com 侯捷《STL源码剖析》 通义 vector…

R语言森林生态系统结构、功能与稳定性分析与可视化实践高级应用

在生态学研究中&#xff0c;森林生态系统的结构、功能与稳定性是核心研究内容之一。这些方面不仅关系到森林动态变化和物种多样性&#xff0c;还直接影响森林提供的生态服务功能及其应对环境变化的能力。森林生态系统的结构主要包括物种组成、树种多样性、树木的空间分布与密度…

如何利用微型5G网关为智慧无人矿车提供精确定位

随着5G、AI、物联网技术的发展和普及&#xff0c;越来越多行业正在加快生产、运营、管理的无人化、数字化与智能化&#xff0c;以适应当前我国“智慧、绿色、低碳”的新型发展模式需要。其中矿产业就是典型场景之一。针对矿山场景的智慧化、无人化转型&#xff0c;佰马提供基于…

我的第一个创作纪念日 —— 梦开始的地方

前言 时光荏苒&#xff0c;转眼间&#xff0c;我已经在CSDN这片技术沃土上耕耘了365天 今天&#xff0c;我迎来了自己在CSDN的第1个创作纪念日&#xff0c;这个特殊的日子不仅是对我过去努力的肯定&#xff0c;更是对未来持续创作的激励 机缘 回想起初次接触CSDN&#xff0c;那…

playwright 学习复仇记-2 Selector选择器定位元素

前言 Selector 选择器&#xff0c;也就是通常说的元素定位了&#xff0c;页面上点点点的操作&#xff0c;都是基于元素定位&#xff0c;所以这块是重点需要学的核心内容。 Selector 选择器 说到元素定位&#xff0c;大家肯定会首先想到 selenium 的八大元素定位&#xff0c;其…

【Delphi】modbus-TCP 协议库

在日常开发中&#xff0c;也会遇到使用modbus的部件&#xff0c;比如温度控制器、读卡器等等&#xff0c;那么使用Delphi开发&#xff0c;也就必须遵守modbus-TCP协议&#xff0c;如果自己使用TCP控件写也没有问题&#xff0c;不过如果有开源的三方库&#xff0c;别人已经调试过…

深度神经网络模型压缩学习笔记一:模型压缩概述

文章目录 一、模型压缩主要常用方法1&#xff09;量化&#xff08;1&#xff09;scale和Qint8的计算、对称性和非对称性量化&#xff08;2&#xff09;静态量化和动态量化&#xff08;3&#xff09;量化粒度选择和PTQ\QAT分类&#xff08;4&#xff09;模型量化技术为什么会带来…

架构师:Dubbo 服务请求失败处理的实践指南

1、简述 在分布式服务中,服务调用失败是不可避免的,可能由于网络抖动、服务不可用等原因导致。Dubbo 作为一款高性能的 RPC 框架,提供了多种机制来处理服务请求失败问题。本文将介绍如何在 Dubbo 中优雅地处理服务请求失败,并结合具体实践步骤进行讲解。 2、常见处理方式 …

YoloV10-yaml文件理解

最近在学习和尝试改进yolov结记录一下原始代码的理解,希望大佬指正.10,在这里总 [1] 论文&#xff1a;https://arxiv.org/abs/2405.14458 [2] 代码&#xff1a;GitHub - THU-MIG/yolov10: YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024] YOLOv10的新特性 1. 无…

Flutter:city_pickers省市区三级联动

pubspec.yaml city_pickers插件地址 自己用的GetBuilder页面模板 cupertino_icons: ^1.0.8 # 省市区城市选择 city_pickers: ^1.3.0编辑地址页面&#xff1a;controller class AddressEditController extends GetxController {AddressEditController();Future<Result?>…

【C语言】结构体(一)

一&#xff0c;是什么 结构体就是一些值的集合&#xff0c;这些值称为成员变量。 结构体的每个成员可以是不同类型的变量。说到集合&#xff0c;数组也是集合&#xff0c;但是不同的是数组只能是相同类型元素的集合。 二&#xff0c;结构体的声明 struct tag {   member1;…

请求头referer #号后的截掉了

解决办法&#xff1a; 用传参方式不要用refer和Refer&#xff0c;用其它名字