我们在stm32f103c8t6单片机上验证RTOS中任务通知API函数的功能,利用stm32cube进行RTOS的配置。裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms 定时,用于任务调度),所以我们开启TIM2当做裸机的时钟源,为其他总线提供另外的时钟源。
我建议看这篇文章的时候,从函数出发。其实就是调用了不同的函数,使用了不同的参数,实现了不同的功能。
1.简介
FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加 省内存(无需创建队列)。 在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!
任务通知值的更新方式 FreeRTOS 提供以下几种方式发送通知给任务 :
①发送消息给任务,如果有通知未读, 不覆盖通知值
②发送消息给任务,直接覆盖通知值
③发送消息给任务,设置通知值的一个或者多个位
④发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
任务通知的优势和劣势
任务通知的优势
① 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
②.使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的劣势
① 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
② 通知只能一对一,因为通知必须指定任务。
③ 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
④ 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据
2.需要用到的函数
模拟模拟二值信号量和计数信号量
发送通知,不带通知值
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
返回值: 总是返回 pdPASS。
获取任务通知
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(二值信号量);pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值 的最大时间;
返回值: 0:接收失败 非0:接收成功,返回任务通知的通知值
模拟事件标志组和消息队列
发送通知,带通知值
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
获取任务通知
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
ulBitsToClearOnExit:表示在函数退 出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参 *pulNotificationValue 中。
pulNotificationValue:用于保存接收到的任务通知值, 如果不需要使 用,则设置为 NULL 即可 。
xTicksToWait:等待消息通知的最大等待时间。
具体的怎么用,我会结合代码说明。
SYS
RCC
GPIO
PA0对应按键1,PA1对应按键2;PB8对应LED1,PB9对应LED2
RTOS
在Tasks and Queues中配置我们的2个任务
其实就是相当于stm32cube帮我们调用了并封装了xTaskCreate()函数。
两个任务的名字分别是
TaskSend,TaskReceive;
四个任务的入口函数名字分别是
StartTaskSend,StartTaskReceive;
其余配置相同,如下图
usart.c
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskSendHandle;
osThreadId TaskReceiveHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskSend(void const * argument);
void StartTaskReceive(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskSend */
osThreadDef(TaskSend, StartTaskSend, osPriorityNormal, 0, 128);
TaskSendHandle = osThreadCreate(osThread(TaskSend), NULL);
/* definition and creation of TaskReceive */
osThreadDef(TaskReceive, StartTaskReceive, osPriorityNormal, 0, 128);
TaskReceiveHandle = osThreadCreate(osThread(TaskReceive), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskSend */
/**
* @brief Function implementing the TaskSend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
xTaskNotifyGive(TaskReceiveHandle);
printf("模拟二值信号量发送成功\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);
}
/* USER CODE END StartTaskSend */
}
}
/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive */
uint32_t rev=0;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
rev=ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
if(rev!=0)
printf("模拟二值信号量接收成功\r\n");
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
}
/* USER CODE END StartTaskReceive */
}
}
在StartTaskSend中使用xTaskNotifyGive(TaskReceiveHandle);模拟信号的发送,参数是TaskReceiveHandle;相当于在StartTaskSend里发送信号,并规定了接收方是谁。
在StartTaskReceive中使用ulTaskNotifyTake(pdTRUE,portMAX_DELAY);参数1是pdTRUE,代表把通知值清零(二值信号量);参数2是时间,代表执行完成函数才结束。同时设立与函数返回值相同的变量uint32_t rev=0,用于接收函数的返回值。0:接收失败 非0:接收成功。
按下KEY1发送成功,按下KEY2接收成功,如下图所示
模拟计数信号量的freertos.c
计数信号量的freertos.c与二值信号量的freertos.c代码几乎形同,不一样的是,要在StartTaskReceive中将ulTaskNotifyTake的参数1改为pdFALSE,代表把通知值清零(计数信号量),其余的不变。 rev=ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
按下KEY1发送一次信号,按下KEY2一次接收信号,每接收一次,都会显示任务通知值共有多少,如下图所示
模拟事件标志组的freertos.c
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskSendHandle;
osThreadId TaskReceiveHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskSend(void const * argument);
void StartTaskReceive(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskSend */
osThreadDef(TaskSend, StartTaskSend, osPriorityNormal, 0, 128);
TaskSendHandle = osThreadCreate(osThread(TaskSend), NULL);
/* definition and creation of TaskReceive */
osThreadDef(TaskReceive, StartTaskReceive, osPriorityNormal, 0, 128);
TaskReceiveHandle = osThreadCreate(osThread(TaskReceive), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskSend */
/**
* @brief Function implementing the TaskSend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
printf("将bit0位置1\r\n");
xTaskNotify(TaskReceiveHandle,0x01,eSetBits);
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);
}
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
printf("将bit1位置1\r\n");
xTaskNotify(TaskReceiveHandle,0x02,eSetBits);
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
}
/* USER CODE END StartTaskSend */
}
}
/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive */
uint32_t notify_val=0,event_bit=0;
/* Infinite loop */
for(;;)
{
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
if(notify_val & 0x01)
event_bit|=0x01;
if(notify_val & 0x02)
event_bit|=0x02;
if( event_bit==(0x01|0x02))
{
printf("任务通知模拟事件标志组接收成功\r\n");
event_bit=0;
}
osDelay(20);
}
/* USER CODE END StartTaskReceive */
}
在StartTaskSend中使用xTaskNotify(TaskReceiveHandle,0x01,eSetBits);模拟信号的发送,参数1是TaskReceiveHandle,相当于在StartTaskSend里发送信号,并规定了接收方是谁;参数2是我们32位事件标志组的每某一位,我们这里要使用的是按下KEY1要使第0位被设置为1,按下KEY2要使第1位被设置为1,所以是参数二分别是0x01和0x02,参数3是eSetBits。
在StartTaskReceive中使用xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY)。
我们希望函数执行前不清零任务通知值的所有为,所以我们谁都不选,因此第一个参数写0;在函数结束后,我们希望清零所有任务通知值,所以将32位的参数2设置为0xFFFFFFFF,清零所有任务通知值;我们还需要通过任务通知值来判断任务是否执行到什么程度,参数3就是负责用于保存接收到的任务通知值,因此我们在设置了StartTaskReceive里设置了uint32_t notify_val=0这个局部变量,用于储存任务通知值。最后一个参数是时间阻塞值,就不多说了。
最后我们还设置了一个临时变量uint32_t event_bit,通过它,将储存在notify_val的数值给调出来,看一看任务通知值的大小。
按下KEY1将bit0位置1,按下KEY2将bit1位置1,两个都按下,相当于将我们所需要的事件都置为1,将会完成模拟事件标志组,如下图所示
模拟消息队列的freertos.c
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskSendHandle;
osThreadId TaskReceiveHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskSend(void const * argument);
void StartTaskReceive(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskSend */
osThreadDef(TaskSend, StartTaskSend, osPriorityNormal, 0, 128);
TaskSendHandle = osThreadCreate(osThread(TaskSend), NULL);
/* definition and creation of TaskReceive */
osThreadDef(TaskReceive, StartTaskReceive, osPriorityNormal, 0, 128);
TaskReceiveHandle = osThreadCreate(osThread(TaskReceive), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskSend */
/**
* @brief Function implementing the TaskSend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskSend */
void StartTaskSend(void const * argument)
{
/* USER CODE BEGIN StartTaskSend */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
{
printf("按键1按下\r\n");
xTaskNotify(TaskReceiveHandle,0x01,eSetValueWithOverwrite);
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);
}
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
osDelay(20);//软件消除按键抖动
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET)
{
printf("按键2按下\r\n");
xTaskNotify(TaskReceiveHandle,0x02,eSetValueWithOverwrite);
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
}
/* USER CODE END StartTaskSend */
}
}
/* USER CODE BEGIN Header_StartTaskReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskReceive */
void StartTaskReceive(void const * argument)
{
/* USER CODE BEGIN StartTaskReceive */
uint32_t notify_val=0,event_bit=0;
/* Infinite loop */
for(;;)
{
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
printf("接收到的值为%d\r\n",notify_val);
event_bit=0;
osDelay(20);
}
/* USER CODE END StartTaskReceive */
}
模拟消息队列的freertos.c与模拟事件标志组的freertos.c也很相似,不一样的地方有两个。
一个是在StartTaskSend中使用
xTaskNotify(TaskReceiveHandle,0x01,eSetValueWithOverwrite);将eSetBits换成了
eSetValueWithOverwrite就可以完成同一个函数从模拟事件标志组到模拟消息队列覆写功能的转换。
另一个是在StartTaskReceive中,我们去掉了event_bit,直接将notify_val,因为我们不需要事件标志组一直积累状态,这里我们来一个被通知任务,就输出一个。
按下KEY1向队列写1,按下KEY2向队列写2,如下图所示
最后要说一点就是,我不知道大家的电脑,尤其是笔记本电脑有没有遇到这种情况。就是无缘无故的不显示电脑网络,而且你尝试了许多办法都没法子解决。没错,我就是这样,我研究所同一级的同学告诉我要换系统,但是说实话,如果换了系统,好多软件都要重新安装,这些科研软件动不动就是几十个G,安装的麻烦死了。后来我看师兄用的无线网卡驱动,我就去淘宝买了个一个。我草,真的管用,如果你也遇到了这种问题,你可以试试,这玩意就是在学校餐厅的一顿饭钱,但是可以帮你解决不麻烦,这个是我在淘宝上购买的地址https://m.tb.cn/h.5ni3Vov?tk=Hal9W5x6ZPO CZ0001 ,客服还挺热情的,可以帮你解决问题,我觉得挺靠谱的,希望可以帮助大家,这个电脑没网络的问题,缠了我一星期了,现在终于解决了。