串口接收:中断程序中逆序打印字符串
串口接收:逆序回环实验思路
注:任务优先级较高会自动的切换上下文进行运行
FreeRTOS中的顶半操作和底半操作
顶半操作和底半操作“这种叫法源自与Linux”在嵌入式开发中,为了和Linux操作系统做一个区分,可以称之为“中断延迟处理”,以下是中断中释放信号量的示意理解图,“顶半操作”实际上就是在中断函数中进行的操作,“中断”-----> 并不属于RTOS系统中自带的,但是中断该产生还是会产生,在实际的开发中我们要尽量的减少频繁的中断,对操作系统的影响,保证器实时性,“底半操作”
实际上就是回到OS中的操作,从顶半操作回到底半操作的需要跨越的屏障可以称之为“裸多屏障”,裸机和多任务之间的屏障,一般在没有主动上下文切换的情况先,任务的切换通常会有一定时间的延时,通常这种情况下我们需要调用主动上下文切换的API“portYIELD()”对上下文进行及时快速的切换以保证系统的实时性(“注:裸多屏障的切换要快”)。
【顶半操作(“中断程序部分”)的设计原则】
注:
1: 中断处理程序ISR,不能介入RTOS的业务
2:ISK运行完之前不可以交出CPU资源
3:尽可能短,越长实时响应性能越差
4:中断程序不参与调度
中断ISR ----------------顶半操作/底半操作--------------【立即处理/迅速完成】 需要大量的时间
在硬件中断中尽快的获取需要的数据给到RTOS进行处理
【串口花样回环实验】
硬件中断的基本原理 :硬件中断可以是由外部的设备进行触发,以下是STM32中提供的中断处理函数,采用弱定义的方式,一般的中断处理函数采用的是递归调用的方式进行处理,使用指针此处使用弱定义的方式可以方便程序开发任务的理解
函数的弱定义_weak
回调函数-----------------> 使用函数指针的方式实现函数的回调
中断中释放信号量失败??什么问题导致,如何在中断程序中释放信号量
在FreeRTOS中如何编写中断程序,中断程序编写原则
1:ISR程序应该尽量简短,迅速返回,不许等待
2:ISR中严禁使用具有阻塞性质的API
“信号量释放API vTaskSemaphoreGive”在FreeRTOS中具有阻塞性质
在FreeRTOS中使用中断专用的API,带FromISR的API
在FreeRTOS中API分为两种,一种是 普通的API一种是中断专用的API“中断专用的API专门用于解决中断问题” 。
关于FromISR API的形参:pxHigherPriorityTaskWoken
中断的API迅速从底半任务,转换为顶半任务,才可以快速的接收(中断程序释放信号量之后,底半任务迅速的运行起来,在这个时间内实现连续的中断串口的接收)
在pxHigherPriorityTaskWoken在调用之后有一个任务立即被运行起来,这个时候有一个中断专用的API会将这个值从0设置为1,如果没有任何任务因为中断中调用这个API,而导致状态发生变化那么它就会保持0这个值而不会设置为1,因此在定义和使用这个变量的时候需要将这个变量的初始化值设置为0,如果中断服务程序退出后,底半任务要立即运行起来就需要执行portYIELD_FROM_ISR 这个中断任务切换的上下文,“过程大概四60ms”
如果程序在中断之中没有主动调用portYIELD_FROM_ISR这个API,任务确实会从阻塞状态变为就绪状态,但是上下文的切换操作会在最近的一次TICK中断中完成,这个时间会有一个延迟,这个长度大概四在200-800ms左右,“根本的问题在于我们在进入中断之后是否需要及时的切换上下文”要看实际中处理的事情是否紧急。
尽量避免频繁的任务切换,“避免频繁的任务切换”,“避免频繁的任务切换”
串口回传数据代码第一部分
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);// 信号量
SemaphoreHandle_t uart_rx_se;// 串口发送的字节数据
uint8_t uart_rx_byte = 0;// 缓冲区
uint8_t usart_rx_buf[100] = {0};
// 计数
uint8_t usart_rx_cnt = 0;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}/*define : 顶半操作,中断处理函数函数的回调,这个时候编译器会放弃原来的弱定义来进行新定义的实现*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 这是一个类型的重定义,实际上是一个long类型portBASE_TYPE taskWoken = 0;// 判断是否是当前串口处理实例if(huart->Instance == USART1){// 尽快的完成中断的处理回到RTOS中// xSemaphoreGive(uart_rx_se);// 第一个参数是xSemaphoreGiveFromISR(uart_rx_se,&taskWoken);HAL_UART_Receive_IT(&huart1,&uart_rx_byte,1);//立即调用这个中断处理函数portYIELD_FROM_ISR(taskWoken);} }void usart_rx_entry(void *p){while(1){xSemaphoreTake(uart_rx_se,portMAX_DELAY);// 缓冲区中放入接收到的字节usart_rx_buf[usart_rx_cnt] = uart_rx_byte;// printf("receive_byte:[%02X]\r\n",usart_rx_buf[usart_rx_cnt]);if(usart_rx_buf[usart_rx_cnt] == 0x0D ||usart_rx_buf[usart_rx_cnt] == 0x0A ){usart_rx_buf[usart_rx_cnt] = 0;// 方便下一次的接收设置为0usart_rx_cnt = 0;printf("string echo : %s\r\n",usart_rx_buf);}else{usart_rx_cnt ++;}}
}// 任务辅助,判断程序是否死掉
void led_toggle_entry(void *p){while(1){HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);vTaskDelay(1000);}
}void task_create_entry(void *p){TaskHandle_t tmp_handle;TaskHandle_t xHandle;TaskHandle_t xHandle2;vTaskDelay(5000);printf("start to create sem\r\n");uart_rx_se = xSemaphoreCreateBinary();printf("process is start");printf("TASK1 IS CREATE\r\n");xTaskCreate(usart_rx_entry,"usart_rx_entry",128,(void *)0,10,&xHandle);// 调用hal库中断方式接收串口的函数HAL_UART_Receive_IT(&huart1,&uart_rx_byte,1);printf("TASK2 IS CREATE\r\n");xTaskCreate(led_toggle_entry,"led_toggle_entry",128,(void *)0,10,&xHandle2);tmp_handle = xTaskGetHandle("task_create_task");vTaskDelete(tmp_handle);}int main(void)
{TaskHandle_t Handle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// shell_init();xTaskCreate(task_create_entry,"task_create_entry",128,(void*)0,12,&Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
以上接收串口数据的方式其实比较原始,效率不高,任务切花也是有代价的
RTOS中使用DMA接收串口数据前导
中断中接收字节改进后的函数参数
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);SemaphoreHandle_t uart_rx_sem;uint8_t uart_rx_byte = 0;uint8_t uart_rx_buf[100] = {0};
uint8_t uart_rx_cnt = 0;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{portBASE_TYPE taskWoken = 0;if(huart->Instance == USART1){
// xSemaphoreGiveFromISR(uart_rx_sem,&taskWoken);HAL_UART_Receive_IT(&huart1,&uart_rx_byte,1);uart_rx_buf[uart_rx_cnt] = uart_rx_byte;//printf("rev_byte:[%02X]\r\n",uart_rx_buf[uart_rx_cnt]);if(uart_rx_buf[uart_rx_cnt] == 0X0D || uart_rx_buf[uart_rx_cnt] == 0X0A){uart_rx_buf[uart_rx_cnt] = 0;uart_rx_cnt = 0;// printf("string echo : %s\r\n",uart_rx_buf);xSemaphoreGiveFromISR(uart_rx_sem,&taskWoken); }else{uart_rx_cnt ++; }portYIELD_FROM_ISR(taskWoken);}
}void uart_rx_entry(void *p)
{while(1){xSemaphoreTake(uart_rx_sem,portMAX_DELAY);// 打印缓冲区中的数据printf("string echo : %s\r\n",uart_rx_buf);}
}void led_toggle_entry(void *p)
{while(1){HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);vTaskDelay(1000);}
}void task_create_entry(void *p)
{ TaskHandle_t tmp_handle; TaskHandle_t xHandle;TaskHandle_t xHandle2;vTaskDelay(5000);//=====================================printf("start to create sems\r\n");uart_rx_sem = xSemaphoreCreateBinary(); printf("start to create tasks\r\n"); printf("create 1st task\r\n");xTaskCreate(uart_rx_entry,"uart_rx_task",128,(void *)0,10,&xHandle);HAL_UART_Receive_IT(&huart1,&uart_rx_byte,1);printf("create 2nd task\r\n");xTaskCreate(led_toggle_entry,"led_toggle_task",128,(void *)0,10,&xHandle);//===================================== tmp_handle = xTaskGetHandle("task_create_task");vTaskDelete(tmp_handle);
}int main(void)
{TaskHandle_t xHandle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();//shell_init();xTaskCreate(task_create_entry,"task_create_task",128,(void *)0,12,&xHandle);vTaskStartScheduler();while (1){}}
嵌入式软件的开发原则:
1:提高任务的运行效率
2:批量的处理是提高效率的最好办法,而不是少量的处理
硬件中断不是RTOS体系中的东西DMA是数据转运,可以让CPU解放出来“缓解CPU的压力”
传输的模式有M2M,M2P模式..............................
使用DMA优化串口接收实验
......后续继续更新