FreeRTOS_同步互斥与通信_队列集_学习笔记

news2024/11/17 16:14:31

FreeRTOS_同步互斥与通信_环形buffer、队列_学习笔记

5.5 队列集

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得到数据后再分别转换为游戏的控制键。
InputTask如何及时读取到多个队列的数据?要使用队列集。

队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:

创建队列A,它的长度是n1
创建队列B,它的长度是n2
创建队列集S,它的长度是“n1+n2”
把队列A、B加入队列集S
这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
InputTask先读取队列集S,它的返回值是一个队列句柄,这样就可以知道哪个队列有有数据了;然后InputTask再读取这个队列句柄得到数据。
创建队列集:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

加入队列到队列集:

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );

读取队列集:

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait);

5.5.1 多控制器实验

在这里插入图片描述

对上一篇笔记的代码进行修改,实现红外遥控和旋转编码器对游戏的共同控制。
在上一篇代码中,直接在中断响应函数IRReceiver_IRQ_Callback中写队列A,
在本例程中,先通过RotaryEncoder_IRQ_Callback(void)写队列B,再通过任务RotaryEncoder_Task读队列B,经过处理后再写队列A

在game1.c中做以下修改:

QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */

static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)]; //定义全局变量,保存队列buffer
static StaticQueue_t g_xQueueRotaryStaticStruct; //定义队列

/* 挡球板任务 */
static void platform_task(void *params)
{
	...
	while (1)
    {
		//if (0 == IRReceiver_Read(&dev, &data))
		xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY);
		...
	}
	...
}
static void RotaryEncoderTask(void *params)//旋转编码器的读写队列和解析
{
	struct rotary_data rdata;
	struct input_data idata;
	int left;
	int i, cnt;
	
	while (1)
	{
		/* 读旋转编码器队列 */
		xQueueReceive(g_xQueueRotary, &rdata, portMAX_DELAY);				
		/* 处理数据 */
		/* 判断速度: 负数表示向左转动, 正数表示向右转动 */
		if (rdata.speed < 0)
		{
			left = 1;
			rdata.speed = 0 - rdata.speed;
		}
		else
		{
			left = 0;
		}
		if (rdata.speed > 100)
			cnt = 4;
		else if (rdata.speed > 50)
			cnt = 2;
		else
			cnt = 1;
				
		/* 写挡球板队列 */
		idata.dev = 1;
		idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
		for (i = 0; i < cnt; i++)
		{
			xQueueSend(g_xQueuePlatform, &idata, 0);
		}
	}
}
void game1_task(void *params)
{		
    ...
	/* 创建队列 */
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); //动态创建队列
	g_xQueueRotary   = xQueueCreateStatic(10, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct); //静态创建队列
	// rotary_data{cnt,speed}和input_data{dev,val}这两个数据结构体在另一个文件中被定义
    //g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct在全局变量区域被定义了
    /*创建旋转编码器的任务,读队列B,解析数据,再写队列A*/
    xTaskCreate(RotaryEncoderTask, "RotaryEncoderTask", 128, NULL, osPriorityNormal, NULL);
    ...
}

在旋转编码器的驱动程序中也需要进行读写队列的修改,driver_rotary_encoder.c如下:

extern QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */
void RotaryEncoder_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	struct rotary_data rdata;
        
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();

    /* 上升沿触发: 必定是高电平 
     * 防抖
     */
    mdelay(2);
    if (!RotaryEncoder_Get_S1())
        return;

    /* S1上升沿触发中断
     * S2为0表示逆时针转, 为1表示顺时针转
     */
    g_speed = (uint64_t)1000000000/(time - pre_time);
    if (RotaryEncoder_Get_S2())
    {
        g_count++;
    }
    else
    {
        g_count--;
        g_speed = 0 - g_speed;
    }
    pre_time = time;
    
	/* 写队列 */
	rdata.cnt   = g_count;
	rdata.speed = g_speed;
	xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}

5.5.2 队列集改良实验

上一个实验中,红外写队列直接作用于任务队列,一般不推荐这么做,因为不方便代码移植。较好的做法是旋转编码器那边的做法,硬件部分管硬件,任务部分管任务。即,旋转编码器中断响应写队列(RotaryEncoder_IRQ_Callback)和旋转编码器任务读写队列(RotaryEncoderTask)分开,这样当旋转编码器要控制别的任务时,只需要修改后者即可,前者不需要修改。

现在将控制器增加为3个,每个控制器都有一个队列并各有一个任务写队列,并用一个Input_Task读取这三个队列并写挡球板队列。Input_Task读取三个队列的方法有两种:轮询和队列集。
如果采用轮询,依次进行读队列操作,那么将不可以设定超时时间(因为超时后才能读下一个队列),这种没有阻塞的轮询会极度浪费CPU资源。
因此使用队列集是合适的,队列集的创建与读写在本文开头说明,在此不再赘述。(队列集s是一个存放队列句柄的队列,写入队列a的时候会顺便把队列句柄写入其所在的队列集s)

因此本实验的改进思路就清晰了:三个控制器分别写三个队列abc,并把abc放到一个队列集s中。Iuput_Task任务读队列集s得到队列句柄,再读队列句柄得到队列abc,再处理数据写入挡球板队列。
在这里插入图片描述
红外接收器驱动程序中做如下修改:

static QueueHandle_t g_xQueueIR; /* 红外队列 */
/*返回红外接收器驱动程序里的队列句柄*/
QueueHandle_t GetQueueIR(void)
{
	return g_xQueueIR;
}
/*在初始化函数中添加创建队列的语句*/
void IRReceiver_Init(void)
{
#if 0
    /*Configure GPIO pin : PB10 */
    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_EVT_RISING_FALLING;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
#endif
	g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
}
/* 在终端回调函数中写队列 */
static int IRReceiver_IRQTimes_Parse(void)
{
	uint64_t time;
	int i;
	int m, n;
	unsigned char datas[4];
	unsigned char data = 0;
	int bits = 0;
	int byte = 0;
	struct ir_data idata;
	/* 1. 判断前导码 : 9ms的低脉冲, 4.5ms高脉冲  */
	time = g_IRReceiverIRQ_Timers[1] - g_IRReceiverIRQ_Timers[0];
	if (time < 8000000 || time > 10000000)
	{
		return -1;
	}
	time = g_IRReceiverIRQ_Timers[2] - g_IRReceiverIRQ_Timers[1];
	if (time < 3500000 || time > 55000000)
	{
		return -1;
	}
	/* 2. 解析数据 */
	for (i = 0; i < 32; i++)
	{
		m = 3 + i*2;
		n = m+1;
		time = g_IRReceiverIRQ_Timers[n] - g_IRReceiverIRQ_Timers[m];
		data <<= 1;
		bits++;
		if (time > 1000000)
		{
			/* 得到了数据1 */
			data |= 1;
		}

		if (bits == 8)
		{
			datas[byte] = data;
			byte++;
			data = 0;
			bits = 0;
		}
	}
	/* 判断数据正误 */
	datas[1] = ~datas[1];
	datas[3] = ~datas[3];	
	if ((datas[0] != datas[1]) || (datas[2] != datas[3]))
	{
        g_IRReceiverIRQ_Cnt = 0;
        return -1;
	}
	/* 写队列 */
	idata.dev = datas[0];
	idata.val = datas[2];	
	xQueueSendFromISR(g_xQueueIR, &idata, NULL);	
    return 0;
}

旋转编码器驱动程序中做如下修改:

static QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */
static uint8_t g_ucQueueRotaryBuf[ROTARY_QUEUE_LEN*sizeof(struct rotary_data)];//用来静态分配的参数
static StaticQueue_t g_xQueueRotaryStaticStruct;
/*返回旋转编码器驱动程序里的队列句柄*/
QueueHandle_t GetQueueRotary(void)
{
	return g_xQueueRotary;
}
/*在初始化函数中创建队列*/
void RotaryEncoder_Init(void)
{
	g_xQueueRotary   = xQueueCreateStatic(ROTARY_QUEUE_LEN, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);
}
/*在中断回调函数中写队列*/
void RotaryEncoder_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	struct rotary_data rdata;   
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();
    /* 上升沿触发: 必定是高电平 
     * 防抖
     */
    mdelay(2);
    if (!RotaryEncoder_Get_S1())
        return;
    /* S1上升沿触发中断
     * S2为0表示逆时针转, 为1表示顺时针转
     */
    g_speed = (uint64_t)1000000000/(time - pre_time);
    if (RotaryEncoder_Get_S2())
    {
        g_count++;
    }
    else
    {
        g_count--;
        g_speed = 0 - g_speed;
    }
    pre_time = time;
    
	/* 写队列 */
	rdata.cnt   = g_count;
	rdata.speed = g_speed;
	xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}

MPU6050驱动程序中做如下修改:

#define MPU6050_QUEUE_LEN 10 /*定义队列长度*/
struct mpu6050_data{int32_t angle_x;};  /*定义陀螺仪数据*/
static QueueHandle_t g_xQueueMPU6050; /*陀螺仪队列*/
/*返回MPU6050驱动程序里的队列句柄*/
QueueHandle_t GetQueueMPU6050(void)
{
	return g_xQueueMPU6050;
}
/*在初始化函数中添加创建队列的语句*/
int MPU6050_Init(void)
{
	MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00);	//解除休眠状态
	MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
	MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);
	MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);
	MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);
	MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);
    
    g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
    
    return 0;
}
/*解析MPU6050数据,通过原生输入(加速度)得到x方向的角度*/
void MPU6050_ParseData(int16_t AccX, int16_t AccY, int16_t AccZ, int16_t GyroX, int16_t GyroY, int16_t GyroZ, struct mpu6050_data *result)
{
	if (result)
	{
		result->angle_x = (int32_t)(acos((double)((double)(AccX + MPU6050_X_ACCEL_OFFSET) / 16384.0)) * 57.29577);
	}
}
/*陀螺仪任务,将当前的角度处理后写队列*/
void MPU6050_Task(void *params)
{
    //MPU6050_Init(); 把陀螺仪初始化函数放到freertos的入口执行
    int16_t AccX; // 这里只关注x方向加速度
    struct mpu6050_data result;
	int ret;
	extern volatile int bInUsed;// 这是判断LCD是否占用的全局变量
    
    while (1)
    {    
		/* 读数据 */
		while (bInUsed);
		bInUsed = 1;
		ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
		bInUsed = 0;
		
        if (0 == ret)
		{
			/* 解析数据 */
			MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);

			/* 写队列 */
			xQueueSend(g_xQueueMPU6050, &result, 0);
		}
		
		/* delay */
		vTaskDelay(50);
	}
}

在game1程序中做如下修改:

static QueueSetHandle_t g_xQueueSetInput; /* 输入设备的队列集 */
static QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
static QueueHandle_t g_xQueueIR; /*红外接收器队列*/
static QueueHandle_t g_xQueueRotary; /*旋转编码器队列*/
static QueueHandle_t g_xQueueMPU6050; /*陀螺仪队列*/
/*挡球板任务*/
static void platform_task(void *params){...}
/*读取红外遥控器键值并转换为游戏控制键,写入挡球板队列*/
static void ProcessIRData(void)
{
	struct ir_data idata; //创建红外接收数据
	static struct input_data input; // 创建挡球板数据
	xQueueReceive(g_xQueueIR, &idata, 0); // 读红外接收器队列
	/*根据红外接收器数据修改挡球板数据*/
	if (idata.val == IR_KEY_LEFT)
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_LEFT;
	}
	else if (idata.val == IR_KEY_RIGHT)
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_RIGHT;
	}
	else if (idata.val == IR_KEY_REPEAT)
	{
		/* 保持不变 */;
	}
	else
	{
		input.dev = idata.dev;
		input.val = UPT_MOVE_NONE;
	}	
	xQueueSend(g_xQueuePlatform, &input, 0); //写挡球板队列
}
/*读取旋转编码器数据并转换为游戏控制键,写入挡球板队列*/
static void ProcessRotaryData(void)
{
	struct rotary_data rdata; // 创建旋转编码器数据
	struct input_data idata; // 创建挡球板数据
	int left;
	int i, cnt;
	xQueueReceive(g_xQueueRotary, &rdata, 0); // 读旋转编码器队列			
	/*根据旋转编码器数据修改挡球板数据*/
	/* 判断速度: 负数表示向左转动, 正数表示向右转动 */
	if (rdata.speed < 0)
	{
		left = 1;
		rdata.speed = 0 - rdata.speed;
	}
	else{left = 0;}	
	if (rdata.speed > 100){cnt = 4;}	
	else if (rdata.speed > 50){cnt = 2;}
	else{cnt = 1;}

	idata.dev = 1;
	idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
	for (i = 0; i < cnt; i++)
	{
		xQueueSend(g_xQueuePlatform, &idata, 0); // 写挡球板队列
	}
}
 /*读取MPU6050D的角度值并转换为游戏控制键,写入挡球板队列*/
static void ProcessMPU6050Data(void)
{
	struct mpu6050_data mdata; // 创建陀螺仪数据
	struct input_data idata; // 创建挡球板数据
	xQueueReceive(g_xQueueMPU6050, &mdata, 0); // 读陀螺仪队列
	/*根据陀螺仪数据修改挡球板数据*/
	/* 判断角度, 大于90度表示往左移动挡球板, 小于90度表示往右移动挡球板 */
	if (mdata.angle_x > 90)
	{
		idata.val = UPT_MOVE_LEFT;
	}
	else if(mdata.angle_x < 90)
	{
		idata.val = UPT_MOVE_RIGHT;
	}
	else
	{
		idata.val = UPT_MOVE_NONE;
	}
	xQueueSend(g_xQueuePlatform, &idata, 0); // 写挡球板队列
}
/*输入任务,检测多个输入设备并调用对应处理函数*/
static void InputTask(void *params)
{
	QueueSetMemberHandle_t xQueueHandle;	
	while (1)
	{
		/* 读队列集, 得到有数据的队列句柄 */
		xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
		if (xQueueHandle)
		{
			/* 读队列句柄得到数据,处理数据 */
			if (xQueueHandle == g_xQueueIR)
			{
				ProcessIRData();
			}
			else if (xQueueHandle == g_xQueueRotary)
			{
				ProcessRotaryData();
			}			
			else if (xQueueHandle == g_xQueueMPU6050)
			{
				ProcessMPU6050Data();
			}			
		}
	}
}
void game1_task(void *params)
{		    
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
	draw_init();
	draw_end();
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); // 创建挡球板队列
	g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN); // 创建队列集	
	g_xQueueIR = GetQueueIR(); // 创建红外接收器队列
	g_xQueueRotary = GetQueueRotary(); // 创建旋转编码器队列
	g_xQueueMPU6050 = GetQueueMPU6050(); // 创建陀螺仪队列
	/*将三个硬件的队列放入队列集*/
	xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
	xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);
	
    xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
    xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
    
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;       
	ball.velX = -0.5;
	ball.velY = -0.6;


	blocks = pvPortMalloc(BLOCK_COUNT);
	memset(blocks, 0, BLOCK_COUNT);
	
	lives = lives_origin = 3;
	score = 0;
	platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();
        vTaskDelay(50);
    }
}	

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

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

相关文章

汇聚荣科技有限公司怎么样?

在众多企业中&#xff0c;汇聚荣科技有限公司以其独特的发展模式和市场定位引起了人们的关注。对于这个问题&#xff0c;答案并非简单的好与坏&#xff0c;而需要从多个维度进行深入分析。 一、公司背景与发展历程汇聚荣科技有限公司成立于何年何地&#xff0c;由谁创立&#x…

如何在go项目中实现发送邮箱验证码、邮箱+验证码登录

前期准备 GoLand &#xff1a;2024.1.1 下载官网&#xff1a;https://www.jetbrains.com/zh-cn/go/download/other.html Postman&#xff1a; 下载官网&#xff1a;https://www.postman.com/downloads/ 效果图(使用Postman) Google&#xff1a; QQ&#xff1a; And …

第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳市解读会议

5月19日&#xff0c;第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳解读活动在阜阳市图书馆隆重举行。共青团阜阳市委员会学少部副部长丁晓龙、阜阳市师范大学物理系副主任黄银生教授、安徽科技报阜阳站站长李伟、市人工智能学会秘书长郭广泽、“北斗杯”安徽…

【html5】02-语义标签

1 引言 语义标签在用法上面与div标签没有区别&#xff0c;但是比div标签没多了层语义性&#xff01;&#xff01; 低版本的浏览器不支持语义标签&#xff0c;即语义标签失效 2 语义标签 <body><!-- <div class"nav"></div><div class&quo…

Lazarus - 从 Hello 开始

我们在《Lazarus - 隐秘的神器》一文中了解到了 Lazarus 的历史和特点&#xff0c;此后将正式开始学习Lazarus 开发。 如果你也对 Windows、Pascal、Delphi 开发感兴趣&#xff0c;请关注 Lazarus专栏 &#x1f4f0; 安装开发环境 官网&#xff1a;Lazarus Homepage (lazarus-i…

智慧园区EasyCVR视频智能管理方案:构建高效安全园区新视界

一、背景分析 园区作为城市的基本单元&#xff0c;是最重要的人口和产业聚集区。根据行业市场调研&#xff0c;90%以上城市居民工作与生活在园区进行&#xff0c;80%以上的GDP和90%以上的创新在园区内产生&#xff0c;可以说“城市&#xff0c;除了马路都是园区”。 园区形态…

Redis的下载、安装、启动和初尝试【超级简单】

redis最好是在Linux系统中使用&#xff0c;这是最接近生产实际的环境。 不过&#xff0c;我们初学者&#xff0c;目的是学习Redis的使用、原理&#xff0c;如果在Linux下直接学习Redis&#xff0c;很可能会因为命令不熟悉而劝退&#xff0c;这是不好的。 因此&#xff0c;我主张…

知乎广告推广开户最低需要多少钱?

精准高效的广告推广&#xff0c;是企业成功的关键&#xff0c;知乎作为知识分享与交流的高端平台&#xff0c;汇聚了大量高质量用户群体&#xff0c;无疑是品牌传播与产品推广的黄金之地。云衔科技作为您数字营销旅程中的得力伙伴&#xff0c;正以专业的知乎广告开户及代运营服…

【企业动态】东胜物联成为AWS硬件合作伙伴,助力实现边缘智能

近日&#xff0c;AIoT硬件设备供应商东胜物联与全球领先的云计算服务提供商亚马逊云&#xff08;AWS&#xff09;达成合作关系&#xff0c;共同致力于推动物联网技术的发展&#xff0c;为企业客户提供更智能、灵活的硬件解决方案&#xff0c;助力智能化升级和数字化转型。 作为…

fping 一键检测局域网内的连接设备(KALI工具系列三)

目录 1、KALI LINUX简介 2、fping工具简介 3、在KALI中使用fping 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、操作示例 4.1 检测主机是否存活 4.2 发送ping包 4.3 指定发送ping包的大小 4.4 生成目标列表 5、总结 1、KALI LINUX简介 Kali Linux …

利用 MongoDB Atlas 进行大模型语义搜索和RAG

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

太速科技-基于5VLX110T FPGA FMC接口功能验证6U CPCI平台

基于5VLX110T FPGA FMC接口功能验证6U CPCI平台 一、板卡概述   本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡。由一片Xilinx公司的XC5VLX110T-1FF1136 / XC5VSX95T-1FF1136 / XC5VFX70T-1FF1136芯片组成。FPGA接1片DDR2内存条 2GB&#xff0c;32MB Nor flash存储器&a…

本特利330878-90-00前置传感器在PLC系统中的应用与优势

本特利330878-90-00前置传感器在PLC系统中的应用与优势 一、引言 在现代工业自动化领域中&#xff0c;传感器作为信息获取的重要工具&#xff0c;其性能的稳定性和准确性直接影响到整个系统的运行效率。其中&#xff0c;本特利330878-90-00前置传感器以其卓越的性能和广泛的应…

Mysql搭建主从同步,docker方式(一主一从)

服务器&#xff1a;两台Centos9 用Docker搭建主从 使用Docker拉取MySQL镜像 确保两台服务器都安装好了docker 安装docker请查看&#xff1a;Centos安装docker 1.两台服务器都先拉取mysql镜像 docker pull mysql 2.我这里是在 /opt/docker/mysql 下创建mysql的文件夹用来存…

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除 1、云端录像1.1、查看录像1.1.1、时间轴视图1.1.2、列表视图1.1.3、日期切换1.1.4、删除当天 1.2、录像计划1.2.1、录像计划列表1.2.2、编辑录像计划1.2.3、关联通道1.2.4、删除录像计…

Steam致富:玩免费游戏Banana获得可交易道具

最近&#xff0c;Steam平台上一款普普通通的免费游戏《Banana》引起了轰动&#xff0c;接近2万人同时在线&#xff0c;好评率高达94&#xff05;&#xff0c;究竟是什么让这款游戏如此受欢迎呢&#xff1f;原来&#xff0c;玩家们都在争相获取稀有的香蕉。 《Banana》属于点击放…

电信光猫获取超级密码,亲测可用

目前多数地区的光猫超级密码都改成动态的了&#xff0c;之前的密码已经无效&#xff0c;也有部分地区还有效。 动态密码目前可通过抓包来获取&#xff0c;比较简单。 抓包获取密码 1. 下载小翼管家&#xff08;建议版本小于5.0&#xff09;并通过WiFi或其他绑定你的光猫。 …

创建型设计模式之建造者模式

文章目录 概述定义建造者模式原理结构图小结 概述 建造者模式又被称为生成器模式&#xff0c;是一种创建型设计模式。 和之前的单例&#xff0c;工厂一样&#xff0c;同属于创建型设计模式。 定义 建造者模式是将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程…

深入理解 Spring 循环依赖之三级缓存(附源码分析)

前言&#xff1a; 学过 Spring 的都知道 Spring 利用三级缓存解决了循环依赖问题&#xff0c;那你知道什么是循环依赖&#xff1f;什么又是三级缓存&#xff1f;本篇将从源码层面分析 Spring 是怎么去利用三级缓存帮我们解决循环依赖问题。 深入理解 Spring IOC 底层实现机制…

Day 38 防火墙技术IPtables

一&#xff1a;防火墙简介 1.简介 ​ iptables其实并不是真正的防火墙&#xff0c;我们可以把他理解为一个客户端的代理&#xff0c;用户是通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的“安全框架”中&#xff0c;这个“安全框架”才是真正的防火墙。这个框…