基于STM32F103ZE平台分析FreeRtos(九)——协程

目录

一、协程简介

二、协程工作机制

2.1 协程控制块结构

2.2 协程管理方式

2.3 协程调度方式

2.4 协程通信机制

三、协程状态及状态切换

3.1 协程状态

3.2 状态切换

四、协程创建

五、协程调度分析

5.1 源码分析

5.2 逻辑图分析

六、协程通信

6.1 协程发送消息(线程)

6.2 协程接收消息(线程)

6.3 向协程发送消息(中断) 

6.4 接收协程消息(中断)


一、协程简介

        FreeRtos中应用既可以使用任务,也可以使用协程(Co-Routine),或者两者混合使用。但是任务和协程使用不同的API函数,因此不能通过队列(信号量)将数据从任务发给协程,反之亦然。

       与任务相比,协程的调度在线程中,不支持抢占调度,只能使用协作式调度,实时精度低;由于没有中断参与,协程具有占用资源少、CPU利用率高的优点。协程与定时器有点类似。

注意:任务与协程混合使用时,协程的调度可以在某个任务中。

二、协程工作机制

2.1 协程控制块结构

        与任务相比较,协程控制块即可表示整个协程,不存在协程栈,也不存在上下文切换。

// 协程的回调函数(参数1:协程控制块句柄,参数2:协程ID uxIndex)
typedef void (*crCOROUTINE_CODE)( CoRoutineHandle_t, UBaseType_t );

// 协程结构体
typedef struct corCoRoutineControlBlock
{
	crCOROUTINE_CODE 	pxCoRoutineFunction;    /*< 入口函数. */
	ListItem_t			xGenericListItem;	    /*< 协程状态条目,用于状态切换. */
	ListItem_t			xEventListItem;		    /*< 协程事项表项,用于信号等阻塞*/
	UBaseType_t 		uxPriority;			    /*< 优先级. */
	UBaseType_t 		uxIndex;			    /*< ID,当多个协程使用相同入口函数时,用于区分协同例程,为回调函数的第二个参数 */
	uint16_t 			uxState;			    /*< 协程状态. */
} CRCB_t; /* 协同程序控制块。Note的大小必须与TCB_t的uxPriority相同. */

2.2 协程管理方式

        协程的管理方式与任务管理类似,通过链表管理,链表定义如下:

/* 就绪和闭锁协程队列. --------------------*/
static List_t pxReadyCoRoutineLists[configMAX_CO_ROUTINE_PRIORITIES];	/*< 就绪链表. */
static List_t xDelayedCoRoutineList1;	/*< 阻塞链表1. */
static List_t xDelayedCoRoutineList2;	/*< 阻塞链表2. */
static List_t * pxDelayedCoRoutineList;	/*< 指向当前阻塞链表 */
static List_t * pxOverflowDelayedCoRoutineList;	/*< 指向溢出阻塞链表. */
static List_t xPendingReadyCoRoutineList;	    /*<  临时就绪链表,*/

        链表结构和操作方式可参考《基于STM32F103ZE平台分析FreeRtos(二)——任务部分》章节。

就绪链表:链接就绪协程,根据协程插入链表的先后顺序排列,新就绪协程插入到链表尾部。

阻塞链表:连接阻塞协程,采用双链表管理,阻塞协程根据阻塞时间片大小,按照从大到小的顺序插入链表,表头指向阻塞时间最近的协程。

双链表管理方式可参考《基于STM32F103ZE平台分析FreeRtos(二)——任务部分》章节。

临时就绪链表:中断中使用,当中断需要释放阻塞协程时,为避免数据冲突,不会直接操作就绪链表,会将释放的协程先插入到临时就绪链表。在协程调度中,由调度器将临时链表中的协程移到正式就绪链表中。

2.3 协程调度方式

        协程只有协作式调度,调度器在线程中循环进行,不存在上下文切换,一个协程执行完成后才能执行下一个协程,每次从协程的回调函数入口执行,每次循环执行一个协程的回调函数。

        调度的原则是选择优先级最高的协程执行,如果优先级最高的协程有多个,则轮询执行这几个协程。

2.4 协程通信机制

        协程支持从中断和线程中以FIFO方式操作消息队列,实现协程间的通信,操作过程与任务类似,但是不能与任务共用消息队列。

        当协程与任务混合使用时,协程调度基于某个任务存在,此时协程可以通过调用任务的通信接口与任务通信,其本质还是任务间的通信。

三、协程状态及状态切换

3.1 协程状态

1、绪( Ready:该协程在就绪链表(pxReadyCoRoutineLists[])或临时就绪链表(xPendingReadyCoRoutineList)中, 就绪的协程已经具备执行的能力,等待调度器调度。

2、行(Running:该协程在就绪列表中,但是正在调度中执行, 调度器选择运行的永远是处于最高优先级的就绪态协程。
3、塞(Blocked: 如果协程正在等待消息,就会从就绪链表移除,并根据阻塞时间插入到阻塞链表中。

3.2 状态切换

1、创建创建→就绪态:协程创建后,根据优先级将协程状态条目连接至就绪链表尾部,等待调度器进行调度。

2、就绪态→运行态:系统调度器启动后(在线程中启动),按照规则依次执行就绪状态的各个协程,当前执行的协程即是运行态;由于调度器在线程中执行,运行态协程不会被其他协程抢占。

3、运行态→就绪态:协程在调度中运行完成后,如果执行过程中没有阻塞,则执行完成切入就绪态,链表无变化。

4、运行态→阻塞态:正在运行的协程发生阻塞(收发消息等待)时,该协程会从就绪列表中移除,并根据阻塞时间片数,设置协程条目xItemValue值(xItemValue=调度入口时间片计数+阻塞时间片数),将该协程依据xItemValue大小插入到阻塞链表,协程由运行态变成阻塞态,然后执行完剩余所有代码后,才退出运行态。

5、阻塞态→就绪态:协程阻塞结束后(阻塞时间到或等待的信号被释放等),此协程会从阻塞链表移除,加入就绪链表,从而由阻塞态变成就绪态。

四、协程创建

        协程只提供动态创建接口xCoRoutineCreate:动态新建协程控制块,可以回收利用。

形参:

pxCoRoutineCode:协程回调函数,回调定义如下,函数有2个形参,形参1:协程控制块句柄;形参2:协程ID(即uxIndex)。

typedef void (*crCOROUTINE_CODE)( CoRoutineHandle_t, UBaseType_t );

uxPriority:协程优先级,数值越大,优先级越高。

uxIndex:协程ID,用于区分不同协程调用同一回调函数,为回调函数的第二个参数。

代码分析:

1. 动态创建协程控制块。
2. 第一个协程创建时,初始化管理链表。
3. 协程控制块根据形参初始化:回调函数、优先级、ID。
4. 初始化链表状态条目xGenericListItem和事项条目xEventListItem,其持有者指向协程控制块句柄。
5. 事件条目值xEventListItem->xItemValue设置为优先级(与控制块优先级相反,数值越小,优先级越高);
6. 协程状态条目xGenericListItem插入到就绪链表尾部,并更新最大优先级。

BaseType_t xCoRoutineCreate( 
crCOROUTINE_CODE pxCoRoutineCode, // 协程回调函数
UBaseType_t uxPriority,           // 协程优先级
UBaseType_t uxIndex )             // 协程ID,用于区分不同协程调用同一回调函数
{
	BaseType_t xReturn;
	CRCB_t *pxCoRoutine;
    //【1】 动态创建协程控制块
	pxCoRoutine = ( CRCB_t * ) pvPortMalloc( sizeof( CRCB_t ) );
	if( pxCoRoutine )
	{
		if( pxCurrentCoRoutine == NULL )
		{
			pxCurrentCoRoutine = pxCoRoutine;
            //【1.1】 第一个协程创建时,初始化管理链表
			prvInitialiseCoRoutineLists();
		}
		// 【2】优先级容错
		if( uxPriority >= configMAX_CO_ROUTINE_PRIORITIES )
		{
			uxPriority = configMAX_CO_ROUTINE_PRIORITIES - 1;
		}
		/* 【3】协程控制块初始化. */
		pxCoRoutine->uxState = corINITIAL_STATE;// 状态
		pxCoRoutine->uxPriority = uxPriority;   // 优先级
		pxCoRoutine->uxIndex = uxIndex;         // ID
		pxCoRoutine->pxCoRoutineFunction = pxCoRoutineCode;// 回调
		/*【4】初始化链表挂接条目. */
		vListInitialiseItem( &( pxCoRoutine->xGenericListItem ) );
		vListInitialiseItem( &( pxCoRoutine->xEventListItem ) );
		/*【5】更新条目持有者。*/
		listSET_LIST_ITEM_OWNER( &( pxCoRoutine->xGenericListItem ), pxCoRoutine );
		listSET_LIST_ITEM_OWNER( &( pxCoRoutine->xEventListItem ), pxCoRoutine );
		/*【6】事件条目设置为优先级。*/
		listSET_LIST_ITEM_VALUE( &( pxCoRoutine->xEventListItem ), ( ( TickType_t ) configMAX_CO_ROUTINE_PRIORITIES - ( TickType_t ) uxPriority ) );
		/*【7】插入到就绪链表,并更新最大优先级*/
		prvAddCoRoutineToReadyQueue( pxCoRoutine );
		xReturn = pdPASS;
	}
	else
	{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}
	return xReturn;
}

五、协程调度分析

5.1 源码分析

1.将临时就绪链表xPendingReadyCoRoutineList中的协程移至正式就绪链表pxReadyCoRoutineLists。

2.协程阻塞判断
(1)取全局时间片计数器xTickCount,并计算重入时间差。

(2)根据时间片遍历阻塞链表,对阻塞时间到的条目,从阻塞链表移除,添加到就绪链表。

(3)如果时间片有翻转现象,对阻塞链表进行切换。

(4)更新协程调度入口时间片xCoRoutineTickCount,协程阻塞时间基于该值计算。

3.调度执行
(1)选择最高优先级协程回调函数执行,如果最高优先级协程有多个,则轮询执行,每次循环执行一个协程的回调。

(2)回调函数形参为运行态协程控制块 pxCurrentCoRoutine和协程控制块识别码pxCurrentCoRoutine->uxIndex。

/*---------------------协程调度(通过循环调度)---------------------*/
void vCoRoutineSchedule( void )
{
	/*【1】临时就绪链表协程移到正式就绪链表*/
	prvCheckPendingReadyList();
	/*【2】阻塞态->就绪态切换,查看是否有阻塞的协同例程超时.*/
	prvCheckDelayedList();
	/*【3】检查最高优先级*/
	while(listLIST_IS_EMPTY(&(pxReadyCoRoutineLists[uxTopCoRoutineReadyPriority])))
	{
		if(uxTopCoRoutineReadyPriority==0)
		{
			return;
		}
		--uxTopCoRoutineReadyPriority;
	}
	/*【4】遍历列表,因此具有相同优先级的协同例程获得相同的处理器时间份额*/
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentCoRoutine, &( pxReadyCoRoutineLists[ uxTopCoRoutineReadyPriority ] ) );
	/*【5】调用协程回调函数*/
	( pxCurrentCoRoutine->pxCoRoutineFunction )( pxCurrentCoRoutine, pxCurrentCoRoutine->uxIndex );
	return;
}
/*-----------------------------------------------------------*/

5.2 逻辑图分析

六、协程通信

        协程通信主要是消息队列的收发,与任务消息队列收发类似,可参考《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》章节学习。

        协程通信接口只能用于协程间通信,不能与任务复用消息队列。

6.1 协程发送消息(线程)

  协程发送消息必须在协程中调用,调用过程不会与其他协程存在数据冲突。

1. 先进入临界区,协程不存在暂停调度的说法,直接闭锁中断,即进入无嵌套临界区。

2. 如果队列满,根据形参阻塞当前协程 ,阻塞时间片为调度器入口时间xCoRoutineTickCount 加阻塞形参xTicksToWaitxCoRoutineTickCount + xTicksToDelay)将当前协程从就绪链表移至阻塞链表,按照阻塞时间片从大到小顺序插入;同时协程事项条目插入到队列等待链表xTasksWaitingToSend;按照优先级从低到高顺序插入。

3. 退出临界区,退出临界区后,可能会有中断接收消息,但不可能有其他协程接收消息。

4. 再次进入临界区,

5. 如果队列未满, 将消息固定拷贝到消息队尾,并判断链表xTasksWaitingToReceive(接收协程阻塞链表)是否有协程阻塞,释放出被阻塞的最高优先级协程(【阻塞态】->【就绪态】)。

6. 退出临界区。

/*-------------协程发送消息(线程)------------------------------*/

BaseType_t xQueueCRSend( QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait )
{
	BaseType_t xReturn;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	// 【1】进入临界区
	portDISABLE_INTERRUPTS();
	{
		// 【2】队列已经满,协程阻塞
		if( prvIsQueueFull( pxQueue ) != pdFALSE )
		{
			// 【2.1.1】阻塞一段时间,协程从就绪链表移除,移入阻塞链表
            // 【2.2.2】阻塞时间为xCoRoutineTickCount + xTicksToWait,xCoRoutineTickCount 为调度入口时间
			if( xTicksToWait > ( TickType_t ) 0 )
			{
				vCoRoutineAddToDelayedList( xTicksToWait, &( pxQueue->xTasksWaitingToSend ) );
				portENABLE_INTERRUPTS();
				return errQUEUE_BLOCKED;
			}
			// 【2.2】无阻塞,直接返回失败
			else
			{
				portENABLE_INTERRUPTS();
				return errQUEUE_FULL;
			}
		}
	}
    // 【3】退出临界区,不会有协程切换,但是中断可能会有消息发送,本协程继续执行!
	portENABLE_INTERRUPTS();
    // 【4】进入临界区
	portDISABLE_INTERRUPTS();
	{
		// 【4.1】队列有空闲
		if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
		{
			//【4.1.2】压入消息
			prvCopyDataToQueue( pxQueue, pvItemToQueue, queueSEND_TO_BACK );
			xReturn = pdPASS;
			/*【4.1.2】是否有等待数据的协程?有的话可以释放(阻塞态->就绪态) */
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive)) == pdFALSE)
			{
				// 释放等待的协程
				if( xCoRoutineRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE )
				{
					xReturn = errQUEUE_YIELD;// 优先级高于当前协程
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		// 【4.2】队列满,返回失败
		else
		{
			xReturn = errQUEUE_FULL;
		}
	}
	portENABLE_INTERRUPTS();

	return xReturn;
}

6.2 协程接收消息(线程)

  协程接收消息必须在协程中调用,调用过程不会与其他协程存在数据冲突。

1. 进入临界区,协程不存在暂停调度的说法,直接闭锁中断,即进入无嵌套临界区。

2. 如队列没有消息,根据形参阻塞当前协程 ,阻塞时间片为调度器入口时间xCoRoutineTickCount 加阻塞形参xTicksToWaitxCoRoutineTickCount + xTicksToDelay)将当前协程从就绪链表移至阻塞链表,按照阻塞时间片从大到小顺序插入;同时协程事项条目插入到队列等待链表xTasksWaitingToReceive;按照优先级从低到高顺序插入。

3.退出临界区,退出临界区后,可能会有中断发送消息,但不可能有其他协程发送消息。

4.再次进入临界区,

5.如果队列有消息, 固定从队列按照FIFO方式读取消息,并判断链表xTasksWaitingToSend(发送协程阻塞链表)是否由阻塞协程,并释放出被阻塞的最高优先级协程(【阻塞态】->【就绪态】)。

6.退出临界区。

/*--------------------- 协程接收消息(线程)--------------------------------------*/
BaseType_t xQueueCRReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait )
{
	BaseType_t xReturn;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
    //【1】进入临界区
	portDISABLE_INTERRUPTS();
	{
		// 【2.1】无消息,阻塞协程
		if( pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0 )
		{
			//【2.1.1】 阻塞时间有效,
			if( xTicksToWait > ( TickType_t ) 0 )
			{
				vCoRoutineAddToDelayedList( xTicksToWait, &( pxQueue->xTasksWaitingToReceive ) );
				portENABLE_INTERRUPTS();
				return errQUEUE_BLOCKED;
			}
			//【2.1.2】 阻塞时间无效,返回失败
			else
			{
				portENABLE_INTERRUPTS();
				return errQUEUE_FULL;
			}
		}
        // 【2.2】 有消息,不阻塞
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
    //【】
	portENABLE_INTERRUPTS();
	portDISABLE_INTERRUPTS();
	{
		// 有消息 读取新消息
		if( pxQueue->uxMessagesWaiting > ( UBaseType_t ) 0 )
		{
			pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
			if( pxQueue->u.pcReadFrom >= pxQueue->pcTail )
			{
				pxQueue->u.pcReadFrom = pxQueue->pcHead;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			--( pxQueue->uxMessagesWaiting );
			( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( unsigned ) pxQueue->uxItemSize );

			xReturn = pdPASS;
			// 发送是否阻塞
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				// 阻塞-就绪
				if( xCoRoutineRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
					xReturn = errQUEUE_YIELD;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	portENABLE_INTERRUPTS();
	return xReturn;
}

6.3 向协程发送消息(中断) 

1. 中断中操作消息先进入临界区(可嵌套)。

2. 如果队列满,退出发送

3. 如果队列未满, 将消息固定拷贝到消息队尾,并判断链表xTasksWaitingToReceive(接收协程阻塞链表)是否有阻塞的协程,并释放出被阻塞的最高优先级的协程(【阻塞态】->【就绪态】);此时释放的协程暂时插入临时就绪链表xPendingReadyCoRoutineList;由协程调度器统一处理。

4.退出临界区。

/*------------------向协程发送消息(中断)-----------------------------------------*/
BaseType_t xQueueCRSendFromISR( QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t xCoRoutinePreviouslyWoken )
{
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
	{
		prvCopyDataToQueue( pxQueue, pvItemToQueue, queueSEND_TO_BACK );
		// 中断唤醒一个接收阻塞的协程
		if( xCoRoutinePreviouslyWoken == pdFALSE )
		{
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
			{
				if( xCoRoutineRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
				{
					return pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	return xCoRoutinePreviouslyWoken;
}

6.4 接收协程消息(中断)

1.中断中操作消息先进入临界区(可嵌套)。

2.如果队列无消息,退出接收

3.如果队列有消息, 固定按照FIFO方式获取消息,并判断链表xTasksWaitingToSend(发送协程阻塞链表)是否有阻塞的协程,并释放出被阻塞的最高优先级的协程(【阻塞态】->【就绪态】);此时释放的协程暂时插入临时就绪链表xPendingReadyCoRoutineList;由线程中的调度器统一处理。

4.退出临界区。

/*--------------------接收协程消息(中断)--------------------------*/
BaseType_t xQueueCRReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxCoRoutineWoken )
{
	BaseType_t xReturn;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	/*我们无法阻止ISR,所以检查是否有可用的数据。如果没有,那就什么都不做就离开 */
	if( pxQueue->uxMessagesWaiting > ( UBaseType_t ) 0 )
	{
		/* 从队列拷贝数据. */
		pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
		if( pxQueue->u.pcReadFrom >= pxQueue->pcTail )
		{
			pxQueue->u.pcReadFrom = pxQueue->pcHead;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		--( pxQueue->uxMessagesWaiting );
		( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( unsigned ) pxQueue->uxItemSize );
		// 唤醒一个发送协程
		if((*pxCoRoutineWoken)== pdFALSE )
		{
			// 移除事项
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				// 恢复阻塞等待发送的协程,更新优先级
				if( xCoRoutineRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
					*pxCoRoutineWoken = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		xReturn = pdPASS;
	}
	else
	{
		xReturn = pdFAIL;
	}
	return xReturn;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Edge的使用心得和深度探索-Sider: ChatGPT 侧边栏

作为一款备受欢迎的网络浏览器&#xff0c;Microsoft Edge在用户体验和功能方面都有着诸多优势。在长期的使用中&#xff0c;我总结出了三条使用心得&#xff0c;同时也发现了三个能够极大提高效率的功能。让我们一起深度探索Edge的潜力吧&#xff01; 使用心得&#xff1a; 界…

Android 10.0 Launcher3定制folder文件夹2x2布局之一xml文件配置和解析相关属性

1.前言 在10.0的系统rom产品定制化开发中,在对Launcher3的folder文件夹功能定制中,要求folder文件夹跨行显示,就是 2x2布局显示,默认的都是占1格的,现在要求占4格显示,系统默认是不支持显示4格的,所以接下来需要分析相关的 功能,然后来实现这个功能 2.Launcher3定制fo…

C# WCF服务(由于内部错误,服务器无法处理该请求。)

由于内部错误&#xff0c;服务器无法处理该请求。有关该错误的详细信息&#xff0c;请打开服务器上的 IncludeExceptionDetailInFaults (从 ServiceBehaviorAttribute 或从 <serviceDebug> 配置行为)以便将异常信息发送回客户端&#xff0c;或打开对每个 Microsoft .NET …

Windows+Linux的虚拟串口工具

文章目录 1.Windows虚拟串口工具1.1 安装教程1.2 使用方法 2.Linux系统虚拟串口工具2.1 socat安装2.2 开启虚拟串口2.3 测试2.3.1 命令测试2.3.2 Cutecom工具测试 2.4 关闭虚拟串口 3.参考资料 1.Windows虚拟串口工具 下载地址&#xff1a;https://www.downxia.com/downinfo/4…

CCF-Csp算法能力认证, 202303-1重复局面(C++)含解析

前言 推荐书目&#xff0c;在这里推荐那一本《算法笔记》&#xff08;胡明&#xff09;&#xff0c;需要PDF的话&#xff0c;链接如下 「链接&#xff1a;https://pan.xunlei.com/s/VNvz4BUFYqnx8kJ4BI4v1ywPA1?pwd6vdq# 提取码&#xff1a;6vdq”复制这段内容后打开手机迅雷…

解决springboot项目的网站静态页面显示不全问题

在通过springboot搭建项目时&#xff0c;为了能够访问静态的前端页面&#xff0c;我们考虑到访问的优先级问题&#xff0c;通常选择将资源放在recourses/static的目录下&#xff0c;如下&#xff1a; 这时可能会出现类似于下面这种图片无法加载、没有按照指定位置显示的情况&am…

Python-100-Days: Day09 Object-oriented programming(OOP) Upgrade

1.property装饰器 之前有讨论过&#xff0c; Python中属性和方法访问权限的问题&#xff0c;不建议将属性设置为私有的&#xff0c;倘若直接将属性暴露给外界也是存在问题的。例如&#xff0c;我们没有办法检查赋给属性的值是否有效。之前的建议是将属性命名以单下划线开头&am…

【线性代数】俗说矩阵听课笔记

基础解系的概念 31线性相关&#xff0c;线性无关&#xff0c;拓展与证明 n个m维向量在n<m时可能线性相关也可能线性无关&#xff0c;线性无关时可以构成某个m维空间的一组基。m不小于n时&#xff0c;秩小于n则线性相关。 n个m维向量在n>m时可一定线性相关。低维向量一定…

microsoft的azure语音,开发环境运行正常,发布到centos7线上服务器之后,无法运行

最近在做AI语音对话的功能&#xff0c;用到了azure的语音语音服务&#xff0c;开发的时候还算顺利&#xff0c;部署到线上后&#xff0c;发现在正式服上无法完成语音转文本的操作&#xff0c;提示&#xff1a; org.springframework.web.util.NestedServletException: Handler d…

2024-05-08 问AI: 在深度学习中,介绍一下RMSProp 优化器

文心一言 在深度学习中&#xff0c;RMSProp&#xff08;Root Mean Square Propagation&#xff09;优化器是一种常用的优化算法&#xff0c;主要用于神经网络训练的梯度下降算法的变体。它是对Adagrad优化器的一种改进&#xff0c;旨在解决Adagrad中学习率过快下降的问题。 R…

HTML学习|初识表单post和get提交、文本框和单选框、按钮、多选框和下拉框、文本域和文件域、搜索框滑块和简单验证、表单的应用、表单初级验证

初识表单post和get提交 form标签是表单&#xff0c;method控制表单提交方式&#xff0c;get方式&#xff0c;表单填写的参数能够在跳转的url地址中看到&#xff0c;post方式是看不到的&#xff0c;action是向何处跳转表单数据 input标签&#xff0c;且typetext&#xff0c;是…

恋爱中的Java多线程:从单身到共舞的浪漫指南(一)

引言&#xff1a;孤独的线程&#xff0c;寂寞的码农 开篇小剧场&#xff1a; ​ 深夜&#xff0c;孤独的程序猿凯叔接到新任务&#xff1a;优化程序性能&#xff0c;探索多线程。这一任务成了他跳出孤独、寻求生活并行美好的契机。从简单的Thread类到复杂的线程池管理&#xff…

基于二维CS-SCHT变换和扩频方法的彩色图像水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................. % figure; % subplot(121);…

GitLab使用记录

GitLab 文章目录 1. 常用命令1.1 配置邮箱 用户名1.2 查看配置1.3 基本语法 2. 连接gitlab3. 直接拉去项目 1. 常用命令 1.1 配置邮箱 用户名 git config --global user.name ShangzheChen git config --global user.email 735511377qq.com1.2 查看配置 cat ~/.gitconfig这…

SpringCloud微服务之Eureka、Ribbon、Nacos详解

SpringCloud微服务之Eureka、Ribbon、Nacos详解 1、认识微服务1.1、单体架构1.2、分布式架构1.3、微服务1.4、SpringCloud 2、服务拆分与远程调用2.1、服务拆分的原则2.2、服务拆分示例2.2、提供者与消费者 3、Eureka注册中心3.1、Eureka的结构和作用3.2、搭建eureka-server3.2…

图像处理:图像噪声添加

文章目录 前言一、高斯噪声二、椒盐噪声三、泊松噪声四、斑点噪声五、指数噪声六、均匀噪声总结 前言 本文主要介绍几种添加图像噪声的方法&#xff0c;用于数据增强等操作。 以下图为例。 一、高斯噪声 高斯噪声就是给图片添加一个服从高斯分布的噪声&#xff0c;可以通过调…

Java | Leetcode Java题解之第77题组合

题目&#xff1a; 题解&#xff1a; class Solution {List<Integer> temp new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> combine(int n, int k) {List&l…

Java | Leetcode Java题解之第78题子集

题目&#xff1a; 题解&#xff1a; class Solution {List<Integer> t new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> subsets(int[] nums) {dfs(0, nums…

Pikachu 靶场 CSRF 通关解析

前言 Pikachu靶场是一种常见的网络安全训练平台&#xff0c;用于模拟真实世界中的网络攻击和防御场景。它提供了一系列的实验室环境&#xff0c;供安全专业人士、学生和爱好者练习和测试他们的技能。 Pikachu靶场的目的是帮助用户了解和掌握网络攻击的原理和技术&#xff0c;…

scala速通(精简版)

1.变量和常量 var name [:VariableType] value // variable val name [:ConstantType] value // constant1.声明变量时&#xff0c;类型可以省略 2.类型定义后就不能修改言 3.变量声明必须有初始值 4.变量&#xff0c;常量分别用var&#xff0c;val声明修饰 2.标识符命名…
最新文章