A引入
A.1移植FreeRTOS至Stm32F103C8T6(标准库)
因为暂时只学了标准库,而正点原子里的教程移植HAL库,所以参考别人https://blog.csdn.net/cairongshou/article/details/129090567来移植。
虽然最后发现移植过程都差不太多emm
B笔记
先附一个图

B.1临界段/区代码保护
什么是临界段:
临界段代码也称临界区,指那些必须完整运行而不能被中断打断的代码段。
适用场合:
1:外设的各种初始化函数,IIC,SPI等等
2:系统自身需求
3:用户需求
什么东西可以打断当前程序的运行
- 中断
- 任务调度 PendSV
PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。由于PendSV的特点就是支持【缓期执行】,所以嵌入式OS可以利用它这个特点,进行任务调度过程的上下文切换。
怎么进入临界区
调用API函数:
1 2 3 4 5 6 7
| taskENTER_CRITICAL();
taskEXIT_CRITICAL();
taskENTER_CRITICAL_FROM_ISR(); taskEXIT_CRITICAL_FROM_ISR();
|
任务级示例:
1 2 3 4 5
| taskENTER_CRITICAL(); { } taskEXIT_CRITICAL();
|
中断级示例:
1 2 3 4 5 6 7 8 9
| void ISR() { uint32_t save_status; save_status = taskENTER_CRITICAL_FROM_ISR(); { } taskEXIT_CRITICAL_FROM_ISR(save_status); }
|
任务调度器的挂起
挂起任务调度器,但是不关闭中断,仅仅是为了防止任务与任务之间的资源争夺。
适合用于临界区在任务与任务之间,不用区延时中断,又可以保证临时区的安全。
1 2 3 4 5
| xTaskSuspendALL(); { } xTaskResumeALL();
|
B.2列表和列表项
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。
列表项就是存放在列表中的项目。
(长的就是个双向循环链表,列表项相当于节点)

优点同链表,成员数量好更改,增删改查
列表和列表项的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VALUE volatile UBaseType_t uxNumberOfItems; ListItem_t * configLIST_VOLATILE pxIndex; MiniListItem_t xListEnd; listSECOND_LIST_INTEGRITY_CHECK_VALUE } List_t;
struct xLIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE configLIST_VOLATILE TickType_t xItemValue; struct xLIST_ITEM * configLIST_VOLATILE pxNext; struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; void * pvOwner; struct xLIST * configLIST_VOLATILE pxContainer; listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE }; typedef struct xLIST_ITEM ListItem_t;
|
迷你列表项/末尾列表项
迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项,类似于链表的头结点
迷你列表项中没有成员变量,以节省开销
1 2 3 4 5 6 7 8
| struct xMINI_LIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE configLIST_VOLATILE TickType_t xItemValue; struct xLIST_ITEM * configLIST_VOLATILE pxNext; struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; }; typedef struct xMINI_LIST_ITEM MiniListItem_t;
|
列表项的初始化和插入等链表的插入基本相似
列表相关的API函数代码详解查看手册《FreeRTOS开发指南》第七章 –“FreeRTos列表和列表项’
B.2任务调度器
API函数
B.3时间片调度
简介
在FreeRTOS中,一个时间片等于 SysTick的中断周期,可以通过设置滴答定时器的中断频率来更改时间片
运行过程
在相同优先级的n个任务,第一个任务运行一个时间片后,切换到第二个任务,第二个任务运行一个时间片后,切换到第三个任务……直到到最后一个任务,碰到阻塞,再运行第一个任务,不断循环。
B.4各种任务相关API函数
在使用 uxTaskGetSystemState() 函数的时候出现了问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| void Task2(void *pvParameters) { UBaseType_t priority_num = 0; priority_num = uxTaskPriorityGet(task1_handler); Serial_Printf("Task1任务优先级:%d\r\n",priority_num); vTaskPrioritySet(task2_handler,23); priority_num = uxTaskPriorityGet(NULL); Serial_Printf("Task2任务优先级:%d\r\n",priority_num);
UBaseType_t task_num = 0; task_num = uxTaskGetNumberOfTasks(); Serial_Printf("任务总数:%d\r\n",task_num); UBaseType_t task_num2; TaskStatus_t * status_array; status_array = pvPortMalloc( task_num * sizeof( TaskStatus_t ) ); task_num2 = uxTaskGetSystemState(status_array,task_num,NULL); Serial_Printf("任务名\t\t任务优先级\t任务编号\r\n"); for(int i=0;i<task_num2;i++) { Serial_Printf("%s\t\t%d\t%d\r\n",status_array[i].pcTaskName, status_array[i].uxCurrentPriority, status_array[i].xTaskNumber); } Serial_Printf("1:%s\r\n",status_array[0].pcTaskName); Serial_Printf("2:%s\r\n",status_array[1].pcTaskName); Serial_Printf("3:%s\r\n",status_array[2].pcTaskName); Serial_Printf("4:%s\r\n",status_array[3].pcTaskName); Serial_Printf("5:%s\r\n",status_array[4].pcTaskName); Serial_Printf("6:%s\r\n",status_array[5].pcTaskName); TaskStatus_t * status_array2; status_array2 = pvPortMalloc( sizeof( TaskStatus_t ) ); vTaskGetInfo( task1_handler,status_array2,pdTRUE,eInvalid);
Serial_Printf("任务名:%s\r\n",status_array2->pcTaskName); Serial_Printf("任务优先级:%ld\r\n",status_array2->uxCurrentPriority); Serial_Printf("任务编号:%ld\r\n",status_array2->xTaskNumber); Serial_Printf("任务状态:%d\r\n",status_array2->eCurrentState); TaskHandle_t task1_handle = 0; task1_handle = xTaskGetHandle("Task1"); Serial_Printf("任务1句柄:%#x\r\n",(int)task1_handle); Serial_Printf("任务1句柄:%#x\r\n",(int)task1_handler);
TaskHandle_t task2_handle = 0; task2_handle = xTaskGetHandle("Task2"); Serial_Printf("任务2句柄:%#x\r\n",(int)task2_handle); Serial_Printf("任务2句柄:%#x\r\n",(int)task2_handler); UBaseType_t task_stack_min = 0;
eTaskState state = 0; state = eTaskGetState(task2_handle); Serial_Printf("任务2当前状态:%d\r\n",state);
vTaskList( task_buff ); Serial_Printf("%s\r\n",task_buff); while(1) { task_stack_min = uxTaskGetStackHighWaterMark(task2_handler); Serial_Printf("task2剩余最小堆栈位%ld\r\n",task_stack_min); vTaskDelay(1000); } }
|
B.5时间统计API函数
在使用时间统计API函数之前,有以下流程
- 将宏
configGENERATE_RUN_TIME_STATS
置 1
- 将宏
configUSE_STATS_FORMATTING_FUNCTIONS
置 1
- 然后还要实现两个宏定义:
- portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(): 用于初始化用于配置任务运行时间统计的时基定时器;
注意:这个时基定时器的计时精度需高于系统时钟节拍精度的10至100倍
- portGET_RUN_TIME_COUNTER_VALUE(): 用于获取该功能时基硬件定时器计数的计数值。
1 2 3 4 5 6 7 8
| #define configGENERATE_RUN_TIME_STATS 1
#include "timer.h" #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats() extern uint32_t FreeRTOSRunTimeTicks; #define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
uint32_t FreeRTOSRunTimeTicks = 0;
void ConfigureTimeForRunTimeStats(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_InternalClockConfig(TIM3); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); TIM_ClearFlag(TIM3, TIM_FLAG_Update); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM3, ENABLE); FreeRTOSRunTimeTicks = 0; }
void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); FreeRTOSRunTimeTicks++; } }
|
然后就可以直接调用vTaskGetRunTimeStats();
函数,使用方法和上面的vTaskList()
使用方法相同。
1 2 3 4
| vTaskGetRunTimeStats(task_buff); Serial_Printf("%s\r\n",task_buff);
|
B.6队列相关函数
这个就直接贴成功运行的代码了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
uint8_t key_num; QueueHandle_t key_queue; QueueHandle_t big_data_queue; char buff[100] = "难道说还是报错";
#define START_TASK_PRIO 1 #define START_TASK_SIZE 64 TaskHandle_t start_task_handler; void Start_Task(void *pvParameters);
#define TASK1_PRIO 2 #define TASK1_SIZE 64 TaskHandle_t task1_handler; void Task1(void *pvParameters);
#define TASK2_PRIO 4 #define TASK2_SIZE 256 TaskHandle_t task2_handler; void Task2(void *pvParameters);
#define TASK3_PRIO 4 #define TASK3_SIZE 256 TaskHandle_t task3_handler; void Task3(void *pvParameters);
void freertos_demo() { key_queue = xQueueCreate( 2, sizeof(uint8_t)); if(key_queue != NULL) { Serial_Printf("小创建成功\r\n"); } else Serial_Printf("小创建失败\r\n"); big_data_queue = xQueueCreate( 1, sizeof(char*)); if(big_data_queue != NULL) { Serial_Printf("大创建成功\r\n"); } else Serial_Printf("大创建失败\r\n");
xTaskCreate( (TaskFunction_t ) Start_Task, (char *) "Start_Task", (configSTACK_DEPTH_TYPE ) START_TASK_SIZE, (void *) NULL, (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); }
void Start_Task(void *pvParameters) { taskENTER_CRITICAL(); Serial_SendString("start_ing\r\n"); xTaskCreate( (TaskFunction_t ) Task1, (char *) "Task1", (configSTACK_DEPTH_TYPE ) TASK1_SIZE, (void *) NULL, (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate( (TaskFunction_t ) Task2, (char *) "Task2", (configSTACK_DEPTH_TYPE ) TASK2_SIZE, (void *) NULL, (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); xTaskCreate( (TaskFunction_t ) Task3, (char *) "Task3", (configSTACK_DEPTH_TYPE ) TASK3_SIZE, (void *) NULL, (UBaseType_t ) TASK3_PRIO, (TaskHandle_t * ) &task3_handler ); vTaskDelete(NULL); taskEXIT_CRITICAL(); }
void Task1(void *pvParameters) { BaseType_t err =0; char *data_to_send = buff; while(1) { taskENTER_CRITICAL(); key_num = Key_GetNum(); if(key_num == 1 || key_num == 2) { err = xQueueSend( key_queue, &key_num, portMAX_DELAY ); if(err != pdTRUE) { Serial_Printf("小发送失败\r\n"); } } else if(key_num == 3) { err = xQueueSend( big_data_queue, &data_to_send, portMAX_DELAY ); if(err != pdTRUE) { Serial_Printf("大发送失败\r\n"); } else { Serial_Printf("大发送成功\r\n"); } } taskEXIT_CRITICAL(); vTaskDelay(10); } }
void Task2(void *pvParameters) { uint8_t key_r = 0; BaseType_t err =0; while(1) { err = xQueueReceive( key_queue, &key_r, portMAX_DELAY ); if(err != pdTRUE) { Serial_Printf("小接收失败\r\n"); } else { Serial_Printf("小数据为:%d\r\n",key_r); } } }
void Task3(void *pvParameters) { char *buf = NULL; BaseType_t err = 0; while(1) { err = xQueueReceive(big_data_queue, &buf, portMAX_DELAY); if(err != pdTRUE) { Serial_Printf("大数据接收失败\r\n"); } else { UBaseType_t task_stack_min = uxTaskGetStackHighWaterMark(task2_handler); Serial_Printf("大数据接收成功\r\n"); Serial_Printf("%s\r\n", buf); } } }
|
暂时只在打印的时候遇到了问题(打印出现乱码),解决方法见C.4;
B.7信号量
信号量简介
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问
信号量(计数值)>1 代表有资源
当释放信号量,计数值就加1
当获取信号量,计数值就减1
信号量的作用就是用来传递状态
信号量的计数值会有限制:限定最大值
如果最大值为1,则它是二值信号量
如果最大值不是1,则它是计数型信号量
二值信号量
二值信号量的本质是一个队列长度为1的队列,队列只有空和满两种情况。
通常用于互斥访问或任务同步,与互斥信号量比较类型,但是二值信号量
有可能会导致优先级翻转的问题,所以更适合用于同步。
创建二值信号量:
动态:xSemaphoreCreateBinary(void)
静态:xSemaphoreCreateBinaryStatic(void)
返回值:NULL 表示创建失败 其它表示二值信号量的句柄
释放二值信号量:相当于把该标志置”满”
任务级:xSemaphoreGive()
中断级:xSemaphoreGiveFromISR()
形参:要释放的信号量句柄,不支持设置阻塞时间
返回值:pdPASS表示释放成功,errQUEUE_FULL表示释放失败
获取二值信号量:相当于把该标志置”空”
任务级:xSemaphoreTake()
中断级:xSemaphoreTakeFromISR()
形参:要释放的信号量句柄,阻塞时间
返回值:pdTRUE表示获取成功,pdFALSE表示获取失败
二值信号量API函数实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
#include "semphr.h" semphore_handle = xSemaphoreCreateBinary(); if(semphore_handle != NULL) { Serial_Printf("创建成功\r\n"); }
void Task1(void *pvParameters) { BaseType_t err = 0; while(1) { taskENTER_CRITICAL(); key_num = Key_GetNum(); if(key_num == 1) { if(semphore_handle != NULL) { err = xSemaphoreGive(semphore_handle); if(err == pdPASS) { Serial_Printf("释放成功\r\n"); } } else { Serial_Printf("释放失败\r\n"); } } taskEXIT_CRITICAL(); vTaskDelay(10); } }
void Task2(void *pvParameters) { uint32_t i = 0; BaseType_t err = 0; while(1) { err = xSemaphoreTake(semphore_handle,portMAX_DELAY); if(err == pdTRUE) { Serial_Printf("获取成功:%d\r\n",i++); } else if(err == pdFALSE) Serial_Printf("获取失败"); } }
|
计数型信号量
计数型信号量相当于队列大于1的队列。因此计数型信号量可以容纳多个资源,队列长度是在创建它时确定的
使用过程:创建计数型信号量->释放信号量->获取信号量
创建二值信号量:
动态:xSemaphoreCreateCounting()
静态:xSemaphoreCreateCountingStatic()
形参:uxMaxCount计数值的最大限定值,uxInitialCount计数值的初始值
返回值:NULL 表示创建失败 其它表示二值信号量的句柄
获取信号量的计数值:
xSemaphoreGetCount()
释放获取信号量的API函数与二值信号量相同
优先级翻转
简介:高优先级的任务反而慢执行,低优先级的任务反而先执行
在抢占式内核中很常见,但是在实时操作系统中是不允许出现的,会破坏任务的预期顺序可能会导致位置的严重后果
在使用二值信号量的时候,可能会导致优先级翻转
*start_task: 创建task1和taks2和task3
*task1: 低优先级任务,先获取信号量,然后执行一段较长时间的任务,然后释放信号量
*task2: 中等优先级任务,简单的应用任务
*task3: 高优先级任务,先获取信号量,然后执行一段拍普通时间的任务,然后释放信号量
比如在运行这个程序的时候,当task1获得信号量时,执行任务的时候,被高优先级task3抢占,但是因为task1还没有释放,所以task3进入阻塞,一直到task1释放,task3才可以运行,优先级发生了翻转,具体现象见实验13
互斥信号量
互斥信号量是一个拥有优先级继承的二值信号量,在同步的应用中使用二值信号量最适合,而互斥信号量适合用于就那些需要互斥访问的应用中。可以用来解决二值信号量的优先级翻转的问题
优先级继承
当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。但是这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
互斥信号量不能用于中断服务函数,整你用于任务中
使用流程:创建互斥信号量->(task)获取信号量->(give)释放信号量
注意互斥信号量创建后会主动释放一次信号量
创建互斥信号量:
动态:xSemaphoreCreateMutex()
静态:xSemaphoreCreateMutexStatic()
形参:uxMaxCount计数值的最大限定值,uxInitialCount计数值的初始值
返回值:NULL 表示创建失败 其它表示二值信号量的句柄
释放获取信号量的API函数与二值信号量相同,但是不支持中断中调用
B.8队列集
用来解决在一个任务中同时要使用多个队列的情况。如果按照之前的用法,在第一个队列没有获得消息时会阻塞,这样下一个队列即使有信息也无法执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
QueueSetHandle_t queueset_handle; QueueHandle_t queue_handle; QueueHandle_t semphr_handle;
queueset_handle = xQueueCreateSet( 2 ); if(queueset_handle != NULL) { Serial_Printf("队列集创建成功\r\n"); } else Serial_Printf("队列集创建失败\r\n"); queue_handle = xQueueCreate( 1, sizeof(uint8_t) ) ; semphr_handle = xSemaphoreCreateBinary(); xQueueAddToSet(queue_handle,queueset_handle); xQueueAddToSet(semphr_handle,queueset_handle);
void Task2(void *pvParameters) { while(1) { uint8_t key_r = 0; QueueSetMemberHandle_t member_handle; member_handle = xQueueSelectFromSet( queueset_handle,portMAX_DELAY); if(member_handle == queue_handle) { xQueueReceive(member_handle,&key_r,portMAX_DELAY); Serial_Printf("获取到的队列数据为%d\r\n",key_r); } else if(member_handle == semphr_handle) { xSemaphoreTake(semphr_handle,portMAX_DELAY); Serial_Printf("获取信号量成功!\r\n"); } } }
|
B.9事件标志组
用一个32位字符的前24位的(0或1)表示事件是否发生,然后执行相关操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
#define EVENTBIT_0 (1 << 0) #define EVENTBIT_1 (1 << 1) #define EVENTBIT_2 (1 << 2)
eventgroup_handle = xEventGroupCreate();
void Task1(void *pvParameters) { while(1) { key_num = Key_GetNum(); if(key_num == 1) { xEventGroupSetBits( eventgroup_handle,EVENTBIT_0); Serial_Printf("0\r\n"); } if(key_num == 2) { xEventGroupSetBits( eventgroup_handle,EVENTBIT_1); Serial_Printf("1\r\n"); } if(key_num == 3) { xEventGroupSetBits( eventgroup_handle,EVENTBIT_2); Serial_Printf("2\r\n"); } vTaskDelay(10); } }
void Task2(void *pvParameters) { EventBits_t event_bit; while(1) { event_bit= xEventGroupWaitBits( eventgroup_handle, EVENTBIT_0 | EVENTBIT_1 | EVENTBIT_2 , pdTRUE, pdTRUE, portMAX_DELAY); Serial_Printf("等待的事件标志位值为:%#x\r\n",event_bit); } }
|
B.10样例实验
- 任务的创建和删除(动态)
- 任务的创建和删除(静态)
- 任务挂起和恢复
- 中断管理
- 列表项的插入和删除实现
- 时间片调度
- 任务相关API函数
- 任务时间统计API函数
- 延时函数延时
- 队列相关API函数
- 二值信号量
- 计数型信号量
- 模拟优先级翻转
- 互斥信号量
- 队列集
- 事件标志组
C遇到的问题
C.1在实验1,2中oled屏幕显示出现部分错位花屏等现象
问题原因:
oled 屏幕显示函数执行的时间不够,因切换任务而被打断。
解决方法:
- 添加函数taskENTER_CRITICAL()和taskEXIT_CRITICAL() 在显示的时候进入临界区,防止被打断。
- 在比oled高优先级任务中**增加vTaskDelay()**函数的时长,这么显示函数的执行时间便会增长。
C.2在实验7中,出现报错:..\OBJ\LED.axf: Error: L6915E: Library reports error: __use_no_semihosting was requested, but _ttywrch was referenced
解决方法
1:在魔术棒中Target选项中 将Use MicroLIB勾选上,然后编译没有报错
问题原因
因为使用了malloc函数,反之如果想使用malloc函数就需要勾选上面这个选项并include “stdlib.h”
C.3在实验7中,调用uxTaskGetSystemState()函数出现问题
问题描述
函数执行完毕后 打印结果里面并没有 task1 任务的信息 或者 是 task1 任务的信息有误(任务编号无穷大/任务名乱码)
尝试直接不创建任务1,其它代码不变
使用vTaskGetInfo() 函数 查询Task1 依旧有错误
问题原因
执行这些查询操作函数的任务2的堆栈爆了,所以报错
使用 uxTaskGetStackHighWaterMark() 函数查询task2的时候发现的
当时定义的64,但实际需使用至少78,所以爆了。
解决方法
使用 uxTaskGetStackHighWaterMark() 函数查询各个任务,将堆栈大小设置在合理范围即可
C.4在实验10中大数据打印出现乱码
问题描述
在打印的时候遇到了问题(打印出现乱码)
Serial_Printf("%s\r\n", buf); // 直接打印接收到的字符串
问题原因
原因是我在创建大数据数组的时候
char buff[100] = "难道说还是报错";
err = xQueueSend(big_data_queue, &buff, portMAX_DELAY);
在 Task1 中将数据 “难道说还是报错” 作为 char* 类型的指针传入队列,
这样会导致队列存储的是一个指向字符串的指针,而不是字符串本身。队列
中的数据是一个指针,接收端 Task3 正确地接收了这个指针,但没有正确地
获取到实际的字符串内容,而导致打印出来可能会出现乱码。
解决方法
在任务1中发送字符串内容(通过指针传递)而不是发送指针本身。
添加一行代码:
char *data_to_send = buff; // 直接使用 buff 数据
修改一行代码:
err = xQueueSend(big_data_queue, &data_to_send, portMAX_DELAY);
D多线程项目
E结尾
参考文献
- 【正点原子】手把手教你学FreeRTOS实时系统
- FreeRTOS官方文档
- 基于STM32F103标准库移植FreeRTOS教程
文件地址
- github仓库
- 网盘地址 提取码: 92jt
留言
有问题请指出,你可以选择以下方式:
- 在下方评论区留言
- 邮箱留言