第14章 uCOS-III操作系统版本二代示波器实现
本章教程为大家讲解uCOS-III操作系统版本的二代示波器实现。主要讲解RTOS设计框架,即各个任务实现的功能,任务间的通信方案选择,任务栈,系统栈以及全局变量共享问题。同时,工程调试方法也专门做了说明。
14.1 注意事项(重要必读)
14.2 任务功能划分
14.3 用户任务优先级设置
14.4 全局变量分配,系统堆栈和任务堆栈
14.5 任务间通信和全局变量共享问题
14.6 uCOS-III系统调试
14.7 MDK优化等级
14.8 总结
14.1 注意事项(重要必读)
1、学习本章节前,务必保证已经学习完毕前面章节。另外,工程代码注释已经比较详细,了解了框架后,直接看源码即可。
2、仅支持800*480分辨率显示屏,如果是电容屏,无需校准。如果是电阻屏,需要校准,按下按键K1即可进入校准界面。
3、由于按键不够用,K1按键的消息处理做了三个条件编译,详情见本章14.6小节。默认K1按键执行触摸校准,也可以选择执行截图或者串口打印任务执行情况。另外,不管当前处于任何界面都可以进行触摸校准,仅电阻屏需要校准,电容屏无需校准。
4、STemWin5.40版本的截图功能有bug,详情看此贴:
http://forum.armfly.com/forum.php?mod=viewthread&tid=82445 。
当前用的5.32版本,也是来自STemWin软件包。
5、Micrium官方曾经发布过一个非常棒的文档,如何发挥uCOS-III最高性能之重要提示和项目应用建议,推荐大家看看:http://forum.armfly.com/forum.php?mod=viewthread&tid=31634 。
6、uCOS-III工程的文件系统是采用的FatFS,当前开启了MDK最高等级优化和时间优化。如果大家要使用FatFS功能,请务必关闭时间优化,因为FatFS在时间优化下会工作异常。详情见本章14.7小节。
7、工程编译支持MDK4.7X和MDK5。另外不支持MDK发布的MDK5.24及其以上版本,因为这个版本不支持MDK4创建的工程转换为MDK5了,所以要使用这个最新的版本,需要给MDK5安装MDK4的兼容包。
14.2 任务功能划分
前面第三章已经将任务功能划分好:
根据这个功能划分,创建所需要的任务。
14.2.1 主函数创建
在main.c文件实现:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main(void) { OS_ERR err; /* 动态内存分配 */ MallocInit(); /* 初始化uC/OS-III 内核 */ OSInit(&err); /* 创建一个启动任务(也就是主任务)。启动任务会创建所有的应用程序任务 */ OSTaskCreate((OS_TCB *)AppTaskStartTCB, /* 任务控制块地址 */ (CPU_CHAR *)"App Task Start", /* 任务名 */ (OS_TASK_PTR )AppTaskStart, /* 启动任务函数地址 */ (void *)0, /* 传递给任务的参数 */ (OS_PRIO )APP_CFG_TASK_START_PRIO, /* 任务优先级 */ (CPU_STK *)&AppTaskStartStk[0], /* 堆栈基地址 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE / 10, /* 堆栈监测区,这里表示后10%作为监测区 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE, /* 堆栈空间大小 */ (OS_MSG_QTY )0, /* 本任务支持接受的最大消息数 */ (OS_TICK )0, /* 设置时间片 */ (void *)0, /* 堆栈空间大小 */ (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), /* 定义如下: OS_OPT_TASK_STK_CHK 使能检测任务栈,统计任务栈已用的和未用的 OS_OPT_TASK_STK_CLR 在创建任务时,清零任务栈 OS_OPT_TASK_SAVE_FP 如果CPU有浮点寄存器,则在任务切换时保存浮点寄存器的内容 */ (OS_ERR *)&err); /* 启动多任务系统,控制权交给uC/OS-III */ OSStart(&err); (void)&err; return (0); }
在主函数中,首先是初始化任务所需的栈空间以及部分全局变量所需的空间,通过函数MallocInit实现(详情看本章节14.4小节)。然后就是创建一个启动任务,其它外设初始化,任务创建,任务消息创建等,都是在启动任务里面实现。
14.2.2 启动任务(触摸和按键)
启动任务实现的功能比较简单,主要是按键扫描和触摸扫描:
/* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 这是一个启动任务,在多任务系统启动后,必须初始化滴答计数器。本任务主要实现按键和触摸检测。 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 3 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg) { OS_ERR err; uint8_t ucCount = 0; /* 仅用于避免编译器告警,编译器不会产生任何目标代码 */ (void)p_arg; /* BSP 初始化。 BSP = Board Support Package 板级支持包,可以理解为底层驱动。*/ CPU_Init(); /* 此函数要优先调用,因为外设驱动中使用的us和ms延迟是基于此函数的 */ bsp_Init(); BSP_Tick_Init(); #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif /* 创建任务通信 */ AppObjCreate(); /* 创建应用程序的任务 */ AppTaskCreate(); while(1) { /* 1ms一次触摸扫描,电阻触摸屏 */ if(g_tTP.Enable == 1) { TOUCH_Scan(); /* 按键扫描 */ ucCount++; if(ucCount == 10) { ucCount = 0; bsp_KeyScan(); } OSTimeDly(1, OS_OPT_TIME_DLY, &err); } /* 10ms一次触摸扫描,电容触摸屏GT811 */ if(g_GT811.Enable == 1) { bsp_KeyScan(); GT811_OnePiontScan(); OSTimeDly(10, OS_OPT_TIME_DLY, &err); } /* 10ms一次触摸扫描,电容触摸屏FT5X06 */ if(g_tFT5X06.Enable == 1) { bsp_KeyScan(); FT5X06_OnePiontScan(); OSTimeDly(10, OS_OPT_TIME_DLY, &err); } } }
在启动任务里,首先做了硬件外设初始化,任务间通信创建和任务创建。然后是触摸和按键扫描。
知识点拓展
新版emWin教程第4章或者第5章,对触摸的实现做了详细讲解:
http://forum.armfly.com/forum.php?mod=viewthread&tid=19834 。
硬件外设的初始化函数bsp_Init是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 使能CRC 因为使用STemWin前必须要使能 */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE); /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); SystemCoreClockUpdate(); /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */ bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitI2C(); /* 配置I2C总线 */ bsp_InitExtSDRAM(); /* 初始化SDRAM */ bsp_DetectLcdType(); /* 检测触摸板和LCD面板型号, 结果存在全局变量 g_TouchType, g_LcdType */ TOUCH_InitHard(); /* 初始化配置触摸芯片 */ LCD_ConfigLTDC(); /* 初始化配置LTDC */ DSO_ConfigCtrlGPIO(); /* 初始化示波器模块的引脚配置 */ bsp_InitADC(); /* 初始化ADC1,ADC2和ADC3 */ bsp_InitDAC1(); /* 初始化DAC1 */ g_DAC1.ucDuty = 50; /* 初始化DAC配置,用于信号发生器 */ g_DAC1.ucWaveType = 0; g_DAC1.ulAMP = 4095; g_DAC1.ulFreq = 10000; dac1_SetSinWave(g_DAC1.ulAMP, g_DAC1.ulFreq); MountSD(); /* 挂载SD卡 */ TIM8_MeasureTrigConfig(); /* 初始化TIM8用于记录一段波形 */ }
任务间的通信创建如下:
/* ********************************************************************************************************* * 函 数 名: AppObjCreate * 功能说明: 创建任务通讯 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppObjCreate (void) { OS_ERR err; /* 创建信号量数值为1的时候可以实现互斥功能,也就是只有一个资源可以使用 本例程是将串口1的打印函数作为保护的资源。防止串口打印的时候被其它任务抢占 造成串口打印错乱。 */ OSSemCreate((OS_SEM *)&AppPrintfSemp, (CPU_CHAR *)"AppPrintfSemp", (OS_SEM_CTR )1, (OS_ERR *)&err); /* 创建计数值为0,用于实现任务同步功能 */ OSSemCreate((OS_SEM *)&SEM_SYNCH, (CPU_CHAR *)"SEM_SYNCH", (OS_SEM_CTR )0, (OS_ERR *)&err); }
任务创建如下:
/* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { OS_ERR err; /**************创建MsgPro任务*********************/ OSTaskCreate((OS_TCB *)AppTaskMsgProTCB, (CPU_CHAR *)"App Task MsgPro", (OS_TASK_PTR )AppTaskMsgPro, (void *)0, (OS_PRIO )APP_CFG_TASK_MsgPro_PRIO, (CPU_STK *)&AppTaskMsgProStk[0], (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_SAVE_FP), (OS_ERR *)&err); /**************创建USER IF任务*********************/ OSTaskCreate((OS_TCB *)AppTaskUserIFTCB, (CPU_CHAR *)"App Task UserIF", (OS_TASK_PTR )AppTaskUserIF, (void *)0, (OS_PRIO )APP_CFG_TASK_USER_IF_PRIO, (CPU_STK *)&AppTaskUserIFStk[0], (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建GUI任务*********************/ OSTaskCreate((OS_TCB *)AppTaskGUITCB, (CPU_CHAR *)"App Task GUI", (OS_TASK_PTR )AppTaskGUI, (void *)0, (OS_PRIO )APP_CFG_TASK_GUI_PRIO, (CPU_STK *)&AppTaskGUIStk[0], (CPU_STK_SIZE )APP_CFG_TASK_GUI_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_GUI_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_SAVE_FP), (OS_ERR *)&err); /**************创建DSO任务*********************/ OSTaskCreate((OS_TCB *)AppTaskDsoTCB, (CPU_CHAR *)"App Task DSO", (OS_TASK_PTR )AppTaskDSO, (void *)0, (OS_PRIO )APP_CFG_TASK_DSO_PRIO, (CPU_STK *)&AppTaskDsoStk[0], (CPU_STK_SIZE )APP_CFG_TASK_DSO_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_DSO_STK_SIZE, (OS_MSG_QTY )5, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_SAVE_FP), (OS_ERR *)&err); }
14.2.3 信号处理任务
信号处理任务的实现如下:
/* ********************************************************************************************************* * 函 数 名: AppTaskDSO * 功能说明: 双通道示波器数据处理任务。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 2 ********************************************************************************************************* */ static void AppTaskDSO (void *p_arg) { OS_ERR err; CPU_TS ts; void *p_msg; OS_MSG_SIZE msg_size; uint32_t *ucReceive, /* 实数序列FFT长度 */ fftSize = 2048; /* 正变换 */ ifftFlag = 0; /* 初始化结构体S中的参数 */ arm_rfft_fast_init_f32(&S, fftSize); while(1) { /* 接受数据 */ p_msg = OSTaskQPend(0, OS_OPT_PEND_BLOCKING, &msg_size, /* 此参数是接收到数据个数 */ &ts, &err); if(err == OS_ERR_NONE) { ucReceive = (uint32_t *)p_msg; switch (*ucReceive) { /* 双通道波形处理 */ case DspFFT2048Pro_15: /* 读取的是ADC3的位置 */ g_DSO1->usCurPos = 10240 - DMA2_Stream1->NDTR; /* 读取的是ADC1的位置 */ g_DSO2->usCurPos = 10240 - DMA2_Stream0->NDTR; DSO2_WaveTrig(g_DSO2->usCurPos); DSO1_WaveTrig(g_DSO1->usCurPos); DSO2_WaveProcess(); DSO1_WaveProcess(); break; /* 用于简单的ADC数据采集 */ case DspMultiMeterPro_14: g_uiAdcAvgSample = ADC_GetSampleAvgN(); break; /* 仅用于调试目的,打印任务的执行情况,默认不使用 */ case DspTaskInfo_13: DispTaskInfo(); break; /* 其它位暂未使用 */ default: App_Printf("*ucReceive = %x ", *ucReceive); break; } } } }
根据接收到的不同任务消息来处理不同的功能,要处理的消息分为三类:
1、双通道波形数据处理
主要实现软件触发,计算FFT ,FIR ,RMS,最大值,最小值,平均值和峰峰值。两个通道都进行了处理。具体实现方法已经在前面章节为大家做了讲解。
2、简单电压测量处理
这个功能比较简单,就是获取一组ADC数值,然后求平均。
3、打印任务执行情况
通过串口打印任务栈的使用情况和各个任务的CPU利用率。
14.2.4 GUI任务
emWin任务的实现代码如下:
/* ********************************************************************************************************* * 函 数 名: AppTaskGUI * 功能说明: GUI任务,最低优先级 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: OS_CFG_PRIO_MAX - 4u ********************************************************************************************************* */ static void AppTaskGUI(void *p_arg) { (void)p_arg; /* 避免编译器告警 */ while (1) { MainTask(); } }
emWin的代码都是在函数MainTask里面实现,这样做是方便在main.c文件里面统一管理任务。关于GUI部分最重要的界面优化,波形刷新优化,波形浏览等,在前面章节已经都做了讲解,我们这里不再赘述。更详细的实现,需要结合前面章节的讲解去看源码。
14.2.5 用户接口任务
这个任务暂时未执行任何功能,保留供以后升级使用。代码如下:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 保留,暂未使用。 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 5 ********************************************************************************************************* */ static void AppTaskUserIF(void *p_arg) { OS_ERR err; (void)p_arg; /* 避免编译器报警 */ while (1) { OSTimeDly(1000, OS_OPT_TIME_DLY, &err); } }
14.2.6 文件系统处理任务
当前文件系统处理任务主要用来做截图功能,将GUI界面以BMP格式存储到SD卡里面:
/* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 实现截图功能,将图片以BMP格式保存到SD卡中 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 4 ********************************************************************************************************* */ static void AppTaskMsgPro(void *p_arg) { uint32_t ulStart, ulEnd; OS_ERR err; uint8_t Pic_Name = 0; char buf[20]; (void)p_arg; while(1) { /* 等待获取信号量同步消息,接收到后实现截图功能,将图片以BMP格式保存到SD卡中 */ OSSemPend((OS_SEM *)&SEM_SYNCH, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); if(err == OS_ERR_NONE) { sprintf(buf,"0:/PicSave/%d.bmp",Pic_Name); /* 记录截图前起始时间 */ ulStart = OSTimeGet(&err); /* 开启调度锁 */ //OSSchedLock(&err); /* 如果SD卡中没有PicSave文件,会进行创建 */ result = f_mkdir("0:/PicSave"); /* 创建截图 */ result = f_open(&file,buf, FA_WRITE|FA_CREATE_ALWAYS); /* 向SD卡绘制BMP图片 */ GUI_BMP_Serialize(_WriteByte2File, &file); /* 创建完成后关闭file */ result = f_close(&file); /* 开启调度锁 */ //OSSchedUnlock(&err); /* 记录截图后时间并获取截图过程耗时 */ ulEnd = OSTimeGet(&err); ulEnd -= ulStart; App_Printf("截图完成,耗时 = %dms ", ulEnd); Pic_Name++; } } }
后期这个任务将被升级,用于将波形数据以CSV文件格式存储到SD卡里面。
14.3 用户任务优先级设置
当前任务的优先级安排如下(数值越小,优先级越高):
App Task DSO任务 : 优先级2。
DSP任务一定要是优先级最高的,因为采集的数据要实时处理。
App Task Start任务 : 优先级3。
App Task MsgPro任务 : 优先级4。
启动任务(触摸和按键扫描)以及MsgPro(文件系统处理)任务的优先级谁高谁低都没有关系。
App Task UserIF任务 :优先级5。
保留,未使用任务,暂且安排为这个优先级。
App Task GUI任务 :优先级OS_CFG_PRIO_MAX - 4u,即32 – 4 = 28
emWin任务是除了空闲任务,统计任务以外最低优先级的,因为emWin极其占用系统资源,而且时间长,如果这个任务设置为高优先级,会直接影响低优先级任务的执行。
知识点拓展
关于任务优先级的安排,在我们RTX操作系统教程第8章的8.2小节有些拓展:
http://forum.armfly.com/forum.php?mod=viewthread&tid=14837。
在我们FreeRTOS操作系统教程的第13章的13.2小节有些拓展:
http://forum.armfly.com/forum.php?mod=viewthread&tid=17658。
14.4 全局变量分配,系统堆栈和任务堆栈
1、全局变量分配
示波器的设计需要很多变量进行逻辑管理,从设计之初就需要将变量分类进行结构体封装,方便以后的维护升级。这一步至关重要,实际中差不多要定义上百个变量,如果不进行分类管理,以后的升级维护将非常麻烦。
这种方式还有一个好处是方便我们将F429的CCM RAM空间分配给这些变量使用。使用CCM RAM的好处是速度比通用RAM要快些,缺点是这部分空间不支持DMA操作。初次使用的用户比较容易在这个地方犯错误。所以在使用局部变量时,切勿将局部变量用于DMA传输。
当前需要频繁调用的变量已经通过动态内存管理分配给各个结构体变量,使用的CCM RAM空间。
uint64_t AppMallocCCM[64*1024/8] __attribute__((at(0x10000000))); /* CCM RAM动态内存池 */ /* ********************************************************************************************************* * 函 数 名: MallocInit * 功能说明: 用户任务栈,用户TCB(任务控制块)和示波器结构体变量使用CCM RAM * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MallocInit(void) { /* 将内部CCM SRAM的64KB全部供动态内存使用 */ rt_init_mem(AppMallocCCM, 1024*64); /* 任务栈和任务控制块TCB的初始化,省略未写 */ /* 申请示波器通道1动态内存 */ g_DSO1 = (DSO_T *)rt_alloc_mem(AppMallocCCM, sizeof(DSO_T)); /* 申请示波器通道2动态内存 */ g_DSO2 = (DSO_T *)rt_alloc_mem(AppMallocCCM, sizeof(DSO_T)); /* 申请游标测量结构体变量动态内存 */ g_Cursors = (CURSORS_T *)rt_alloc_mem(AppMallocCCM, sizeof(CURSORS_T)); /* 申请标志位结构体变量动态内存 */ g_Flag = (FLAG_T *)rt_alloc_mem(AppMallocCCM, sizeof(FLAG_T)); /* 申请触发结构体变量动态内存 */ g_TrigVol = (TRIVOLTAGE_T *)rt_alloc_mem(AppMallocCCM, sizeof(TRIVOLTAGE_T)); /* 申请FFT动态内存 */ testInput_fft_2048 = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*2048); testOutput_fft_2048 = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*2048); /* 申请RMS动态内存 */ g_RMSBUF = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*600); /* 申请FIR动态内存 */ FirDataInput = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*FIR_LENGTH_SAMPLES); FirDataOutput = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*FIR_LENGTH_SAMPLES); firStateF32 = (float32_t *)rt_alloc_mem(AppMallocCCM, sizeof(float32_t)*FIR_StateBufSize); }
2、任务栈和任务控制块TCB
任务栈和任务控制块TCB也是用的CCM RAM空间,具体分配如下:
uint64_t AppMallocCCM[64*1024/8] __attribute__((at(0x10000000))); /* CCM RAM动态内存池 */ /* ********************************************************************************************************* * 函 数 名: MallocInit * 功能说明: 用户任务栈,用户TCB(任务控制块)和示波器结构体变量使用CCM RAM * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MallocInit(void) { /* 将内部CCM SRAM的64KB全部供动态内存使用 */ rt_init_mem(AppMallocCCM, 1024*64); /* 任务堆栈和任务控制块是使用CCM RAM */ AppTaskDsoTCB = (OS_TCB *)rt_alloc_mem(AppMallocCCM, sizeof(OS_TCB)); AppTaskDsoStk = (CPU_STK *)rt_alloc_mem(AppMallocCCM, sizeof(CPU_STK)*APP_CFG_TASK_DSO_STK_SIZE); AppTaskStartTCB = (OS_TCB *)rt_alloc_mem(AppMallocCCM, sizeof(OS_TCB)); AppTaskStartStk = (CPU_STK *)rt_alloc_mem(AppMallocCCM, sizeof(CPU_STK)*APP_CFG_TASK_START_STK_SIZE); AppTaskMsgProTCB = (OS_TCB *)rt_alloc_mem(AppMallocCCM, sizeof(OS_TCB)); AppTaskMsgProStk = (CPU_STK *)rt_alloc_mem(AppMallocCCM, sizeof(CPU_STK)*APP_CFG_TASK_MsgPro_STK_SIZE); AppTaskUserIFTCB = (OS_TCB *)rt_alloc_mem(AppMallocCCM, sizeof(OS_TCB)); AppTaskUserIFStk = (CPU_STK *)rt_alloc_mem(AppMallocCCM, sizeof(CPU_STK)*APP_CFG_TASK_USER_IF_STK_SIZE); AppTaskGUITCB = (OS_TCB *)rt_alloc_mem(AppMallocCCM, sizeof(OS_TCB)); AppTaskGUIStk = (CPU_STK *)rt_alloc_mem(AppMallocCCM, sizeof(CPU_STK)*APP_CFG_TASK_GUI_STK_SIZE); /* 部分全局变量的初始化,省略未写 */ }
知识点拓展
关于任务栈大小应该分配多大的问题,可以看FreeRTOS教程第11章,对于uCOS-III系统也是适用的:http://forum.armfly.com/forum.php?mod=viewthread&tid=17658 。
3、系统栈分配
系统栈的大小不是在启动文件里面配置,因为系统启动过程中做了重新配置,所以启动文件里面配置的系统栈只在uCOS-III开启多任务之前使用:
重新配置的系统栈需要在如下所示位置设置,单位4字节,比如这里是配置的512u,也就是2048字节。
14.5 任务间通信和全局变量共享问题
二代示波器的双通道ADC通过DMA方式在实时的采集数据,每个通道的缓冲大小是1024*20字节,采集的数据经过信号处理后送给GUI任务进行波形显示和测量值显示。为了实现这个功能,专门测试了两种方案。
(1)方案一
采用DMA双缓冲,一路缓冲采集波形的时候,另一路已经采集的波形数据发给数字信号处理任务,信号处理任务再将整理好的波形数据和测量值发给emWin任务做刷新。这种方式的优点是ADC采集的数据可以实时处理。缺点是F429处理不过来,比如我们一个通道的采样率是2Msps,缓冲大小设置为2048,将缓冲填满需要1ms左右的时间,而我们仅做一个2048点的实数FFT就需要0.862ms,其它的FIR,RMS等都还没有做,而且已经没有时间发消息给emWin任务做界面刷新了。如果我们降低FFT,FIR等信号处理的点数,也就失去了实时处理的意义。也许读者会说,加大缓冲不就好了,其实不然。如果我们加大了缓冲,我们要处理的数据也增加了,还是处理不过来,而且我们现在要处理的是双通道。
除了F429的性能问题,这种方式还有一个比较棘手的问题需要解决,就是用户操作界面的时候,GUI任务基本已经没有时间去处理数字信号处理任务发来的数据,为了解决这个问题,大大增加了软件设计的复杂度,特别是波形暂停和运行的切换,窗口的切换以及其它操作时,都要注意这个问题。
如果没有复杂的界面操作,而且采样率较低的话,方案一还是比较合适的。由于我们需要滑动操作波形,而且要实现双通道,每个通道最高采样率是2.8Msps,所以放弃这种方案。
(2)方案二
与方案一恰恰相反,ADC数据依然是通过DMA方式实时采集,而任务间的通信反过来进行,emWin任务需要波形数据刷新时给数字信号处理任务发消息获取,这样就有效地解决了方案一中F429性能不够的问题,而且方案一中棘手的软件问题得到了很好的解决,随时都可以操作界面。
并且这种方式无形中解决了emWin任务和数字信号处理任务之间共同操作全局变量的问题,因为emWin是低优先级任务,而数字信号处理任务在emWin任务发消息后才会执行,这样就不存在抢占问题了,有效地解决了全局变量共享问题。
但是这种方式也有一个缺陷,无法实时刷新波形和测量值了,不过可以通过普通触发来解决了,普通触发方式实时采集了触发值前后各1024字节的数据,并且可以滑动浏览。不过工程中未对这种方式做FFT和FIR的支持。
总结,二代示波器中最终选择了方案二。
14.6 uCOS-III系统调试
调试uCOS-III有两种方法,一种是uC/Probe,还有一种是串口打印。
1、uC/Probe调试
uCOS-III的调试推荐使用uC/Probe,性能强劲。针对uC/Probe的调试,我们专门做过一个专题教程,如果没有用过的话,务必先学习教程。
知识点拓展
【专题教程第2期】uC/Probe简易使用说明,含MDK和IAR,支持F103,F407和F429开发板
http://forum.armfly.com/forum.php?mod=viewthread&tid=50280 。
另外特别注意一个问题,二代示波器的uCOS-III做了最高等级的优化和时间优化,使用uC/Probe调试需要关闭所有优化,否则无法使用,因为调试信息都被优化掉了。
2、串口打印调试
串口打印比较简单,按下按键K1即可。不过由于按键不够用,在MainTask.c文件的MainTask函数里面对按键K1的消息处理做了三个条件编译,大家可以根据需要选择执行触摸校准功能,截图功能还是串口打印功能。#if 0取消执行,#if 1表示执行。
case KEY_1_DOWN: #if 1 hTouchWin = WM_CreateWindowAsChild(0, 0, 800, 480, WM_HBKWIN, WM_CF_SHOW, _cbTouchCalibration, 0); WM_Exec(); WM_SelectWindow(hTouchWin); /* 执行触摸校准 */ TOUCH_Calibration(); WM_SelectWindow(0); WM_DeleteWindow(hTouchWin); WM_Exec(); /* 自动触发暂停状态 */ if(g_Flag->hWinRunStop == 1) { g_Flag->ucWaveRefresh = 1; } /* 普通触发暂停状态 */ if(TriggerFlag == 1) { TriggerFlag = 2; } #endif /* 用于截图 */ #if 0 OSSemPost((OS_SEM *)&SEM_SYNCH, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); #endif /* 打印任务的执行情况 */ #if 0 g_Flag->ucDsoMsg = DspTaskInfo_13; OSTaskQPost(AppTaskDsoTCB, (void *)&g_Flag->ucDsoMsg, sizeof(g_Flag->ucDsoMsg), OS_OPT_POST_FIFO, &err); #endif break;
如果使能了串口打印的条件编译,串口打印任务执行情况如下:
14.7 MDK优化等级
为了发挥STM32F429的最高性能,需要大家开启最高等级优化和时间优化,即下面两个选项:
uCOS-III工程的文件系统是采用的FatFS,当前开启了最高等级的三级优化和时间优化。如果大家要使用FatFS功能,请务必关闭时间优化,即Optimize for Time,取消勾选即可。因为FatFS在时间优化下会工作异常。
知识点拓展
MDK曾经做的专题:如何做MDK编译器的代码最小优化和性能最佳优化。
http://forum.armfly.com/forum.php?mod=viewthread&tid=1794 。
14.8 总结
uCOS-III系统设计二代示波器的关键问题在本章节都做了阐释,建议大家学习完本章节后,直接看源码做实战演练,这样理解的更透彻,而且这时再做改进拓展也容易些。