tag 标签: freertos

相关帖子
相关博文
  • 2025-4-17 16:27
    0 个评论
    【原创奖励】ESP开发之ubuntu环境搭建
    1. 在Ubuntu官网下载Ubuntu server 20.04版本 https://releases.ubuntu.com/20.04.6/ 2. 在vmware下安装Ubuntu 3. 改Ubuntu静态IP $ sudo vi /etc/netplan/00-installer-config.yaml # This is the network config written by 'subiquity' network: renderer: networkd ethernets: ens33: #dhcp4: true addresses: - 192.168.4.251/24 nameservers: addresses: routes: - to: default via: 192.168.4.1 version: 2 $ sudo netplan apply $ ip addr show ens33 $ ip route show $ reboot 4. 使用SecureCRT使用SSH远程连接虚拟机的ubuntu 5. 安装各种必要的工具 sudo apt-get install git wget flex bison gperf python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 net-tools 6. 新建esp32目录并进入 $ mkdir esp32 $ cd esp32 7. 拉取gitee工具(原因是从官方下载大概率会失败) $ git clone https://gitee.com/EspressifSystems/esp-gitee-tools.git 8. 执行gitee工具切换镜像脚本 $ cd esp-gitee-tools $ ./jihu-mirror.sh set 9. 拉取esp-idf源码 $ cd .. $ git clone --recursive https://github.com/espressif/esp-idf.git 10. 切换esp-idf版本分支到v5.3 $ cd esp-idf $ git checkout v5.3 $ git submodule update --init --recursive 如果提示失败或有错误试下这句:../esp-gitee-tools/submodule-update.sh 11. 更换pip源 $ pip config set global.index-url http://mirrors.aliyun.com/pypi/simple $ pip config set global.trusted-host mirrors.aliyun.com 12. 安装编译工具 $ ../esp-gitee-tools/install.sh 13. 设置环境变量并将环境变量放到.bashrc中(这样下次启动后上一步设置的环境变量会重新加载) $ source export.sh $ echo "source ~/esp32/esp-idf/export.sh" ~/.bashrc 14. 设置USB串口权限(解决下载代码时报USB串口权限问题) $ sudo usermod -aG dialout usrname usrname需要换成你的用户名 15. 重启 16. windows安装VSCODE,并安装如下插件 17. 通过以上插件远程连接ubuntu 这样就可以建立远程连接了,代码就可以在vscode进行查看和更改,但是我们会发现每次都需要输入密码,我们通过如下方式解决 解决使用VsCode远程ssh连接虚拟机ubuntu需要重复输入密码 18. VSCode中为远程主机安装插件 19. 按照上图依次安装如下插件 20. 进行插件配置 c/c++插件 改ESP-IDF配置 键盘同时按下ctl+shift+p,在弹出的对话框输入如下,这样我们就可以任意查看和跳转代码位置了。 21. 建立第一个工程 使用vscode远程连接虚拟机,在终端窗口进行操作 创建存放工程的目录并进入目录 mkdir esp32-prj cd esp32-prj 使用命令创建工程 idf.py create-project helloworld 进入目录更改目标芯片,一般默认为esp32 idf.py set-target esp32-s3 进行第一次编译 idf.py build 打开工程 如图操作,这样才可以方便的跳转和查看代码,每个新的工程都需要执行一次 编译代码 烧录,先连接开发板,并保证开发板是连接到了虚拟机 idf.py flash 监视开发板执行情况 idf.py monitor 退出监控使用CTL+]组合键 烧录指令可以和监控指令可以放在一起执行 idf.py flash monitor
  • 热度 5
    2020-8-23 17:10
    4147 次阅读|
    3 个评论
    定时器 硬件定时器: CPU内部自带的定时器模块,通过初始化、配置可以实现定时,定时时间到以后就会执行相应的定时器中断处理函数。硬件定时器一般都带有其他功能,比如PWM输出、输入捕获等功能。但缺点是硬件定时器数量少!!! 软件定时器: 软件定时器允许设置一段时间,当设置的时间到达以后就执行指定的功能函数,被定时器调用的这个功能函数就叫做定时器的回调函数。回调函数的两次执行间隔,就叫做定时器的定时周期。简而言之,当定时器的定时周期到了就会执行回调函数。 回调函数的注意事项: 软件定时器的 回调函数是在定时器服务任务中执行 的,所以一定 不能在回调函数中调用任何会阻塞任务的 API 函数 !比如,定时器回调函数中 千万不能调用 vTaskDelay()、vTaskDelayUnti() ,还有一些 访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。 定时器服务 FreeRTOS软件定时器的API函数,大多都 使用队列发送命令给定时器服务任务 。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问!通信过程如下所示: 通信过程详解: 定时器服务任务,是在任务调度器中创建的: 在创建定时器服务任务中,有创建定时器任务的函数: 定时器服务任务函数分析: 定时器消息队列的命令,是在第3个函数中处理的。 其中,创建队列的函数如下所示: 首先创建了一个定时器消息队列结构体: 接下来创建定时器命令消息队列: 定时器相关配置: 之前提到,软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,配置方法和之前其他条件编译一样,而且相关的配置也是放到文件 FreeRTOSConfig.h 中的,涉及到的配置如下: 单次定时器和周期定时器: 定时器相关的操作与API函数: 1 复位软件定时器 2 创建软件定时器: 这个宏 portTICK_PERIOD_MS 非常有用,如下所示: # define configTICK_RATE_HZ ((TickType_t)1000) # define portTICK_PERIOD_MS ((TickType_t)1000 / configTICK_RATE_HZ) 其中,configTICK_RATE_HZ 为每秒的 Tick 频率,portTICK_PERIOD_MS 则为每 Tick 一次需要多少 ms(周期ms)。 这个宏定义,可以用于 将时间转换成 Tick 数,即 Tick 数 = 总时间 / (每次 Tick 的时间) 如我们需要 延时800ms ,则 Tick_count = 800 / (portTICK_PERIOD_MS) 3 开启定时器: 4 停止定时器: 测试实验 软件定时器测试实验: 创建两个任务, start_task 用于创建 timer_ctrl_task 和两个软件定时器; 周期定时器(周期1000节拍),单次定时器(周期2000节拍) timer_ctrl_task 任务通过检测2个按键,来开始或者停止两个软件定时器; 程序很简单,如下所示: TimerHandle_t auto_reload_timer_handle; // 周期定时器句柄 TimerHandle_t one_shot_timer_handle; // 单次定时器句柄 void start_task ( void *pvParameters) { taskENTER_CRITICAL(); // 创建周期定时器 auto_reload_timer_handle = xTimerCreate(( char * ) "auto_reload_timer" , (TickType_t )( 1000 / portTICK_PERIOD_MS), // 定时1秒 (UBaseType_t )pdTRUE, // 周期 ( void * ) 1 , (TimerCallbackFunction_t)auto_reload_timer_callback); // 创建单次定时器 one_shot_timer_handle = xTimerCreate(( char * ) "one_shot_timer" , (TickType_t )( 2000 / portTICK_PERIOD_MS), // 定时2秒 (UBaseType_t )pdFALSE, // 单次 ( void * ) 2 , (TimerCallbackFunction_t)one_shot_timer_callback); // 创建定时器控制任务 xTaskCreate((TaskFunction_t )timer_ctrl_task, ( char * ) "timer_ctrl_task" , ( uint16_t )TIMER_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TIMER_TASK_PRIO, (TaskHandle_t * )&timer_task_Handle); vTaskDelete(Start_Task_Handle); taskEXIT_CRITICAL(); } void timer_ctrl_task ( void *pvParameters) { printf ( "\r\ntimer_ctrl_task start!\r\n" ); printf ( "press KEY1 to start auto_reload_timer\r\n" ); printf ( "press KEY2 to start one_shot_timer\r\n" ); for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { // 发送队列阻塞时间设置为0 if (xTimerStart(auto_reload_timer_handle, 0 ) == pdFAIL) { printf ( "auto_reload_timer start failed\r\n" ); } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { // 发送队列阻塞时间设置为0 if (xTimerStart(one_shot_timer_handle, 0 ) == pdFAIL) { printf ( "one_shot_timer start failed\r\n" ); } } vTaskDelay( 50 ); } } // 周期定时器回调函数 void auto_reload_timer_callback (TimerHandle_t pxTimer) { static uint16_t times = 0 ; printf ( "auto_reload_timer running = %d times\r\n" , ++times); } // 单次定时器回调函数 void one_shot_timer_callback (TimerHandle_t pxTimer) { static uint16_t times = 0 ; printf ( "one_shot_timer running = %d times\r\n" , ++times); } 程序运行结果如下: 多定时器共用回调函数: 前面提到,创建定时器时有一个pvTimerID,可以用于区分不同的定时器,如下所示: 函数原型如下所示: 其实就是根据定时器句柄,来获取 TimerID 通过在回调函数中调用这个函数获取ID,就能直到当前是哪个定时器调用回调函数,如下所示: // 定时器共用回调函数 void timer_common_callback (TimerHandle_t pxTimer) { static uint16_t one_shot_times = 0 ; static uint16_t auto_reload_times = 0 ; if (pvTimerGetTimerID(pxTimer) == ( void *) 1 ) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pvTimerGetTimerID(pxTimer) == ( void *) 2 ) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } } 测试结果和之前分开写回调函数一致,如下所示: 既然创建定时器时,已经给每个定时器创建了句柄,那么定时器回调函数中,应该也可以直接通过句柄来进行当前是那个定时器在调用回调函数,如下所示: // 定时器共用回调函数 void timer_common_callback (TimerHandle_t pxTimer) { static uint16_t one_shot_times = 0 ; static uint16_t auto_reload_times = 0 ; # if 0 if (pvTimerGetTimerID(pxTimer) == ( void *) 1 ) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pvTimerGetTimerID(pxTimer) == ( void *) 2 ) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } # else if (pxTimer == auto_reload_timer_handle) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pxTimer == one_shot_timer_handle) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } # endif } 两种方式的运行结果相同,但推荐使用获取ID的方式,因为这是FreeRTOS推荐的。 转载于: https://blog.csdn.net/dingyc_ee/article/details/104118508
  • 热度 4
    2020-8-23 17:05
    2331 次阅读|
    0 个评论
    计数型信号量简介: 计数型信号量的创建: 计数型信号量动态创建函数: 释放和获取信号量(与二值信号量相同) 释放信号量: 获取信号量: 测试实验 用按键来模拟事件,按键按下后表示有事件发生,则释放计数型信号量。 创建两个任务,任务1用于按键检测和释放信号量,按键2用于获取信号量。 测试程序如下所示: void start_task ( void *pvParameters) { taskENTER_CRITICAL(); count_semphr = xSemaphoreCreateCounting( 5 , 0 ); if (count_semphr != NULL ) { printf ( "计数型信号量创建成功\n" ); } else { printf ( "计数型信号量创建失败\n" ); } // 创建任务1 xTaskCreate((TaskFunction_t ) task1_task, ( char * ) "task1_task" , ( uint16_t ) TASK1_TASK_SIZE, ( void * ) NULL , (UBaseType_t ) TASK1_TASK_PRIO, (TaskHandle_t * ) &Task1_Handle); // 创建任务2 xTaskCreate((TaskFunction_t )task2_task, ( char * ) "task2_task" , ( uint16_t )TASK2_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK2_TASK_PRIO, (TaskHandle_t * )&Task2_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(Start_Task_Handle); } void task1_task ( void *pvParameters) { BaseType_t error_state; for (;;) { // 按键KEY1用于释放信号量 if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { if (count_semphr != NULL ) { error_state = xSemaphoreGive(count_semphr); if (error_state == pdTRUE) { printf ( "信号量释放成功\n" ); } else { printf ( "信号量释放失败\n" ); } } } vTaskDelay( 20 ); } } void task2_task ( void *pvParameters) { BaseType_t error_state; UBaseType_t current_semphr_value; for (;;) { // 按键KEY2用于获取信号量 if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { if (count_semphr != NULL ) { // 等待获取信号量 error_state = xSemaphoreTake(count_semphr, portMAX_DELAY); if (error_state == pdTRUE) { printf ( "获取信号量成功\n" ); current_semphr_value = uxSemaphoreGetCount(count_semphr); printf ( "当前信号量计数值为%d\n" , ( int )current_semphr_value); } else { printf ( "获取信号量失败\n" ); } } } vTaskDelay( 50 ); } } 测试结果: 转载于: https://blog.csdn.net/dingyc_ee/article/details/104114161
  • 热度 4
    2020-8-23 16:54
    2338 次阅读|
    0 个评论
    信号量 1 信号量用于共享资源的访问: 2 信号量用于任务同步: 为什么一直说在中断服务函数中,不能够做太多的事情? 在进入中断服务函数时,低优先级的中断就不能响应,同类型的中断也无法响应,所以就要求ISR一定要短,快进快出。 最好的解决方案时,在中断服务函数中发送一个信号量,在任务中等待信号量,实现任务同步。 二值信号量 二值信号量简介: 二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。 二值信号量执行流程: 创建信号量: 动态创建二值信号量: 实际上,信号量就是通过队列来实现的,源码如下: # define semSEMAPHORE_QUEUE_ITEM_LENGTH ((uint8_t)0U) # if (configSUPPORT_DYNAMIC_ALLOCATION == 1) # define xSemaphoreCreateBinary() xQueueGenericCreate((UBaseType_t)1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE) # endif 实际上,就是调用创建队列的函数。 信号量的创建方式如下所示: 释放信号量: 获取信号量: 获取函数参数说明: 中断中获取信号量: 二值信号量测试: 串口中断服务函数中接收数据,接收完成后(IDLE)释放二值信号量,任务中获取信号量,根据串口数据来响应。 程序如下所示: uint8_t RX_BUFFER ; // 串口接收缓冲区 SemaphoreHandle_t binary_semaphore = NULL ; // 二值信号量句柄 void start_task ( void *pvParameters) { taskENTER_CRITICAL(); binary_semaphore = xSemaphoreCreateBinary(); // 创建信号量 if (binary_semaphore != NULL ) { printf ( "信号量创建成功!\n" ); } else { printf ( "信号量创建失败!\n" ); } // 创建任务1 xTaskCreate((TaskFunction_t )task1_task, ( char * ) "task1_task" , ( uint16_t )TASK1_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK1_TASK_PRIO, (TaskHandle_t * )&task1_task_Handle); // 创建任务2 xTaskCreate((TaskFunction_t )task2_task, ( char * ) "task2_task" , ( uint16_t )TASK2_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK2_TASK_PRIO, (TaskHandle_t * )&task2_task_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(start_Task_Handle); } void task1_task ( void *pvParameters) { for (;;) { printf ( "task1 running\r\n" ); vTaskDelay( 1000 ); } } void task2_task ( void *pvParameters) { uint16_t count = 0 ; BaseType_t err_state; for (;;) { if (binary_semaphore != NULL ) { // 一直等待,直到获取到二值信号量 err_state = xSemaphoreTake(binary_semaphore, portMAX_DELAY); if (err_state == pdTRUE) { if ( strcmp (( char *)RX_BUFFER, "LED_ON" ) == 0 ) { LED_RED; } if ( strcmp (( char *)RX_BUFFER, "LED_OFF" ) == 0 ) { LED_ALL_OFF; } if ( strcmp (( char *)RX_BUFFER, "BEEP_ON" ) == 0 ) { BEEP_ON; } if ( strcmp (( char *)RX_BUFFER, "BEEP_OFF" ) == 0 ) { BEEP_OFF; } memset (RX_BUFFER, 0 , USART_BUFFER_LEN); } } printf ( "we get %d times binary\n" , ++count); vTaskDelay( 20 ); } } void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; BaseType_t pxHigherPriorityTaskWoken; BaseType_t error_state; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,释放信号量 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; if (binary_semaphore != NULL ) { error_state = xSemaphoreGiveFromISR(binary_semaphore, &pxHigherPriorityTaskWoken); if (error_state != pdTRUE) { printf ( "二值信号量释放失败!\n" ); } // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } } // 清除空闲中断(依次读取SR DR) SR; DR; } } 测试结果: 转载于: https://blog.csdn.net/dingyc_ee/article/details/104108665
  • 热度 25
    2020-8-23 16:49
    2461 次阅读|
    0 个评论
    队列简介: 注意,队列长度并不是每个队列的字节数,而是能存多少条数据。如我们创建一个队列,每条消息最大10字节,队列可以保存4条消息,那么队列的长度为4。 队列的功能: 1 数据拷贝 为什么一定要使用数据拷贝? 考虑以下情况,我们在任务中创建了一个局部变量,而局部变量可能随时会被删掉,此时如果使用引用传递(指针),数据可能就会出现问题;另外,如果采用指针,则在这个变量被队列引用之前,变量值是不能改变的,这就意味着此时任务将不能继续随意使用此变量;如果采用数据拷贝,则不会出现上述问题。 2 多任务访问 队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。 3 出队阻塞 4 入队阻塞 队列操作过程 1 创建队列 2 写入队列 3 读取队列 队列结构体原型: typedef struct QueueDefinition { int8_t *pcHead; /* 指向队列存储区开始地址 */ int8_t *pcTail; /* 指向队列存储区最后一个字节 */ int8_t *pcWriteTo; /* 指向存储区中下一个空闲区域 */ union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */ { int8_t *pcReadFrom; /* 当用作队列的时候指向最后一个出队的队列项首地址 */ UBaseType_t uxRecursiveCallCount; /* 当用作递归互斥量的时候用来记录递归互斥量被调用的次数 */ } u; List_t xTasksWaitingToSend; /* 等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上 */ List_t xTasksWaitingToReceive; /* 等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上 */ volatile UBaseType_t uxMessagesWaiting; /* 队列中当前队列项数量,也就是消息数 */ UBaseType_t uxLength; /* 创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量 */ UBaseType_t uxItemSize; /* 创建队列时指定的每个队列项(消息)最大长度,单位字节 */ /* 当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */ volatile int8_t cRxLock; /* 当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */ volatile int8_t cTxLock; # if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1)) uint8_t ucStaticallyAllocated; /* 如果使用静态存储的话此字段设置为 pdTURE */ # endif # if (configUSE_QUEUE_SETS == 1) struct QueueDefinition * pxQueueSetContainer ; # endif # if (configUSE_TRACE_FACILITY == 1) UBaseType_t uxQueueNumber; uint8_t ucQueueType; # endif } xQUEUE; typedef xQUEUE Queue_t; 创建队列 1 动态创建队列 函数原型: 实现的功能,就是动态内存申请需要大小的内存 = 消息数 × 消息长度,然后将队列存储区指针偏移到缓存区。 QueueHandle_t xQueueGenericCreate ( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType) { Queue_t *pxNewQueue; size_t xQueueSizeInBytes; uint8_t *pucQueueStorage; (UBaseType_t) 0 ); if (uxItemSize == (UBaseType_t) 0 ) { /* There is not going to be a queue storage area. */ xQueueSizeInBytes = ( size_t ) 0 ; } else { /* Allocate enough space to hold the maximum number of items that can be in the queue at any time. */ xQueueSizeInBytes = ( size_t )(uxQueueLength * uxItemSize); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ } pxNewQueue = (Queue_t *)pvPortMalloc( sizeof (Queue_t) + xQueueSizeInBytes); if (pxNewQueue != NULL ) { /* Jump past the queue structure to find the location of the queue storage area. */ pucQueueStorage = (( uint8_t *)pxNewQueue) + sizeof (Queue_t); # if (configSUPPORT_STATIC_ALLOCATION == 1) { /* Queues can be created either statically or dynamically, so note this task was created dynamically in case it is later deleted. */ ucStaticallyAllocated = pdFALSE; } # endif /* configSUPPORT_STATIC_ALLOCATION */ prvInitialiseNewQueue(uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue); } return pxNewQueue; } 函数说明: 静态创建队列: 入队函数: 入队函数说明: 函数参数说明: 通用发送函数简单分析: 数据拷贝过程: 中断级队列发送: 下面是正点原子串口中断中发送队列的程序: void USART1_IRQHandler ( void ) //串口1中断服务程序 { u8 Res; BaseType_t xHigherPriorityTaskWoken; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res = USART_ReceiveData(USART1); //读取接收到的数据 if ((USART_RX_STA & 0x8000 ) == 0 ) //接收未完成 { if (USART_RX_STA & 0x4000 ) //接收到了0x0d { if (Res != 0x0a ) USART_RX_STA = 0 ; //接收错误,重新开始 else USART_RX_STA |= 0x8000 ; //接收完成了 } else //还没收到0X0D { if (Res == 0x0d ) USART_RX_STA |= 0x4000 ; else { USART_RX_BUF = Res; USART_RX_STA++; if (USART_REC_LEN - 1 )) USART_RX_STA = 0 ; //接收数据错误,重新开始接收 } } } } //就向队列发送接收到的数据 if ((USART_RX_STA & 0x8000 ) && (Message_Queue != NULL )) { xQueueSendFromISR(Message_Queue, USART_RX_BUF, &xHigherPriorityTaskWoken); //向队列中发送数据 USART_RX_STA = 0 ; memset (USART_RX_BUF, 0 , USART_REC_LEN); //清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要的话进行一次任务切换 } } 其实这个程序,个人觉得局限性比较大,因为它规定了数据格式。总体思路是, 在串口接收中断中,将接收到的数据一个一个的保存到缓冲区里,等待串口数据接收完毕(可以通过读取寄存器标志位,串口空闲标志位来判断接收完毕)时,将缓冲区里的数据发送到队列中,然后清空缓冲区,并根据相关变量确定是否需要进行任务切换。 出队函数 删除读取队列函数参数说明: 不删除读取队列函数参数说明: 同样的,中断读取队列函数: 中断中进行任务切换的判别函数: portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 队列实验 设计2个任务,其中按键任务用于获取键值,并发送至队列;按键处理任务获取队列中的消息,并处理键值。 按键队列任务如下所示: key_queue = xQueueCreate( 1 , sizeof ( uint8_t )); // 创建按键队列 if (key_queue == NULL ) { printf ( "按键队列创建失败!\n" ); } void key_task ( void *pvParameters) { uint8_t key_value; BaseType_t error_value; for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { key_value = 1 ; if (key_queue != NULL ) { // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列 error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { key_value = 2 ; if (key_queue != NULL ) { error_value = xQueueSendToBack(key_queue, &key_value, 10 ); // 发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } vTaskDelay( 50 ); } } void print_task ( void *pvParameters) { uint8_t rx_key_value; BaseType_t rx_queue_state; for (;;) { if (key_queue != NULL ) { // 等待从队列中读取数据 rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "key_value = %d\n" , rx_key_value); switch (rx_key_value) { case 1 : LED_RED; break ; case 2 : LED_BLUE; break ; default : LED_ALL_OFF; break ; } } } vTaskDelay( 50 ); } } 测试结果如下所示: 中断服务函数使用队列 这个比较复杂,总体思路是,在串口接受回调函数中,将接收到的数据保存至RX_BUFFER;在空闲中断中,将RX_BUFFER的数据全部发送至队列;在队列处理任务中,将队列中的数据读到TX_BUFFER中,然后打印输出。 1 使能接收中断和空闲中断 // 使能串口接收中断和空闲中断,并清除标志位 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE); __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); 2 接收中断处理中,将接收到的字符保存至RX_BUFFER缓冲区 // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } 整个串口中断服务函数如下所示: void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } } 读取串口消息队列数据的任务如下所示: void usart_task ( void *pvParameters) { BaseType_t rx_queue_state; for (; ;) { if (usart_queue != NULL ) { // 等待从串口消息队列中读取数据 rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "%s\n" , TX_BUFFER); memset (TX_BUFFER, 0 , USART_QUEUE_LEN); } } vTaskDelay( 50 ); } } 注意,这个实验要求,串口的中断优先级可以被屏蔽,即 ≥ 5(5为预设的屏蔽优先级,只有这样,才能在中断服务函数中使用fromISR函数,发送队列),如下所示: 测试结果如下所示: 完整的工程代码: main.c # include "main.h" # include "i2c.h" # include "usart.h" # include "gpio.h" # include "bsp_led.h" # include "bsp_key.h" # include "bsp_usart.h" # include "FreeRTOS.h" # include "task.h" # include "queue.h" # include # define USART_QUEUE_DEPTH 1 # define USART_QUEUE_LEN 100 void start_task ( void *pvParameters) ; void key_task ( void *pvParameters) ; void print_task ( void *pvParameters) ; void usart_task ( void *pvParameters) ; /* 任务相关参数 */ # define START_TASK_SIZE 128 # define START_TASK_PRIO 1 TaskHandle_t start_Task_Handle; # define KEY_TASK_SIZE 128 # define KEY_TASK_PRIO 2 TaskHandle_t key_task_Handle; # define PRINT_TASK_SIZE 128 # define PRINT_TASK_PRIO 3 TaskHandle_t print_task_Handle; # define USART_TASK_SIZE 128 # define USART_TASK_PRIO 3 TaskHandle_t usart_task_Handle; /* 队列相关参数 */ QueueHandle_t key_queue; QueueHandle_t usart_queue; uint8_t RX_BUFFER ; // 串口接收缓冲区 uint8_t TX_BUFFER ; // 串口发送缓冲区 int main ( void ) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_I2C2_Init(); // 使能串口接收中断和空闲中断,并清除标志位 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE); __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); // 创建开始任务 xTaskCreate((TaskFunction_t )start_task, ( char * ) "start_task" , ( uint16_t )START_TASK_SIZE, ( void * ) NULL , (UBaseType_t )START_TASK_PRIO, (TaskHandle_t * )&start_Task_Handle); vTaskStartScheduler(); // 开启调度器 } void start_task ( void *pvParameters) { taskENTER_CRITICAL(); /* 创建按键队列 */ key_queue = xQueueCreate( 1 , sizeof ( uint8_t )); if (key_queue == NULL ) { printf ( "按键队列创建失败!\n" ); } /* 创建串口消息队列 */ usart_queue = xQueueCreate(USART_QUEUE_DEPTH, USART_QUEUE_LEN); if (usart_queue == NULL ) { printf ( "串口消息队列创建失败!\n" ); } // 创建按键任务 xTaskCreate((TaskFunction_t )key_task, ( char * ) "key_task" , ( uint16_t )KEY_TASK_SIZE, ( void * ) NULL , (UBaseType_t )KEY_TASK_PRIO, (TaskHandle_t * )&key_task_Handle); // 创建消息处理任务 xTaskCreate((TaskFunction_t )print_task, ( char * ) "print_task" , ( uint16_t )PRINT_TASK_SIZE, ( void * ) NULL , (UBaseType_t)PRINT_TASK_PRIO, (TaskHandle_t * )&print_task_Handle); // 创建串口队列任务 xTaskCreate((TaskFunction_t )usart_task, ( char * ) "usart_task" , ( uint16_t )USART_TASK_SIZE, ( void * ) NULL , (UBaseType_t )USART_TASK_PRIO, (TaskHandle_t * )&usart_task_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(start_Task_Handle); } void key_task ( void *pvParameters) { uint8_t key_value; BaseType_t error_value; for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { key_value = 1 ; if (key_queue != NULL ) { // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列 error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { key_value = 2 ; if (key_queue != NULL ) { error_value = xQueueSendToBack(key_queue, &key_value, 10 ); // 发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } vTaskDelay( 50 ); } } void print_task ( void *pvParameters) { uint8_t rx_key_value; BaseType_t rx_queue_state; for (;;) { if (key_queue != NULL ) { // 等待从队列中读取数据 rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "key_value = %d\n" , rx_key_value); switch (rx_key_value) { case 1 : LED_RED; break ; case 2 : LED_BLUE; break ; default : LED_ALL_OFF; break ; } } } vTaskDelay( 50 ); } } void usart_task ( void *pvParameters) { BaseType_t rx_queue_state; for (; ;) { if (usart_queue != NULL ) { // 等待从串口消息队列中读取数据 rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "%s\n" , TX_BUFFER); memset (TX_BUFFER, 0 , USART_QUEUE_LEN); } } vTaskDelay( 50 ); } } 中断服务函数: void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } } 转载于: https://blog.csdn.net/dingyc_ee/article/details/104096636
相关资源