RTCSD2017-2
解决方案
任务简述
本次作业主体由三个任务组成,分别是Sender,Receiver,Monitor。分别完成发送,接收和监控的任务。
Sender
Sender作为数据生产者,要求每隔2ms生成一个逐次递增的数。当增加到10000时,返回1继续累加。
Receiver
Receiver作为数据消费者,要求每隔1s统计一次生成的数的总和。
Monitor
Monitor作为监控者,要求每隔10s检查一次数据是否成功接收。
思路简述
- 由作业一可知,可以用FreeRTOS提供的延时函数
vTaskDelay()
来完成周期性的任务。 - 由上述要求,Receiver执行两次之间,Sender会执行500次,即产生500个数。所以需要缓冲区来承载这些数。
- 不妨选择队列来作为缓冲区,即FreeRTOS提供的
xQueueCreate()
等一系列函数。而且这种队列也支持多任务同时读写。 - 之后需要确定监控的方式。可以发现,Receiver产生的数都是依次递增的,前后两个之间相隔1。
- 所以Sender可以将当前从队列里取到的数与上一个数比较,将两个的差减一后累加到某个值上去。如果这个值不为0,则出现问题。
- 如果后一个数比前一个数小,则将后一个数加上10000再做以上操作。比如当前收到1,上一个收到的是10000:(1+10000)-10000=1,1-1=0
- 为了更好地表述,记第5步中所说的值为Diff。当前Diff由Sender累加,但Monitor也会去访问。由于不确定累加和访问这两个操作的原子性,所以需要对这两个加锁。
- 不妨选择
xSemaphoreCreateMutex()
作为锁。
代码展示
/**
* Sender_Task: Product the number
*/
void SenderTask(void* arg)
{
traceString stLogger = xTraceRegisterString("Sender Task");
uint32_t uiNum = 1;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1)
{
if (pdPASS != xQueueSend(g_hNumberBuff, &uiNum, 0))
{
vTracePrintF(stLogger, "Send to queue failed.");
if (pdPASS == xSemaphoreTake(g_uiMutexSL, 1))
{
g_uiFailed++;
vTracePrintF(stLogger, "Fail: %d", g_uiFailed);
xSemaphoreGive(g_uiMutexSL);
}
}
uiNum += 1;
if (uiNum > 10000)
{
uiNum = 1;
}
vTaskDelayUntil(&xLastWakeTime, 2 / portTICK_RATE_MS);
}
}
/**
* Receiver_Task: Produce the number
*/
void ReceiverTask(void* arg)
{
traceString stLogger = xTraceRegisterString("Receiver Task");
uint64_t ulTmpSum = 0;
uint32_t uiNumber = 0;
uint32_t uiLast = 0;
uint32_t uiTmpDiff;
uint32_t uiTmpLoopCnt;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1)
{
uiTmpDiff = 0;
uiTmpLoopCnt = 0;
ulTmpSum = 0;
while (pdPASS == xQueueReceive(g_hNumberBuff, &uiNumber, 0))
{
ulTmpSum += uiNumber;
if (uiNumber > uiLast)
{
uiTmpDiff += uiNumber - uiLast - 1;
}
else
{
uiTmpDiff += uiNumber + 10000 - uiLast - 1;
uiTmpLoopCnt += 1;
}
uiLast = uiNumber;
}
if (pdPASS == xSemaphoreTake(g_uiMutexRML, 10 / portTICK_RATE_MS))
{
g_uiDiff += uiTmpDiff;
g_uiLoopCnt += uiTmpLoopCnt;
g_ulSum += ulTmpSum;
xSemaphoreGive(g_uiMutexRML);
}
vTracePrintF(stLogger, "The sum of this round is %u\n", ulTmpSum);
vTaskDelayUntil(&xLastWakeTime, 1000 / portTICK_RATE_MS);
}
}
/**
* Monitor_Task: Check the task
*/
void MonitorTask(void* arg)
{
traceString stLogger = xTraceRegisterString("Monitor Task");
uint32_t uiTmpDiff;
uint32_t uiTmpLoopCnt;
uint32_t uiTmpState;
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1)
{
uiTmpState = 0;
if (pdPASS == xSemaphoreTake(g_uiMutexRML, 10))
{
uiTmpDiff = g_uiDiff;
uiTmpLoopCnt = g_uiLoopCnt;
g_uiState = (uiTmpDiff != 0);
xSemaphoreGive(g_uiMutexRML);
}
if (uiTmpDiff == 0)
{
vTracePrintF(stLogger, "OK, %d loops are done.", uiTmpLoopCnt);
}
else
{
STM_EVAL_LEDOn(LED4);
STM_EVAL_LEDOff(LED3);
vTracePrintF(stLogger, "Wrong, difference is %d.", uiTmpDiff);
}
vTaskDelayUntil(&xLastWakeTime, 10000 / portTICK_RATE_MS);
}
}
成果展示
GitHub
https://github.com/89yanyu/STM32F429I-Discovery
仿真结果
开始是绿灯,一段时间后变为红灯。
因为只有在第二次运行Monitor的时候才会检查到有错误,将灯的状态改变。
- 初始化(红,绿灯亮)
- 正常运行(绿灯亮)
- 初始化失败或检查到错误(红灯亮)
运行结果
标准模式
正常运行中:
出现错误但未被Monitor检测到:
Monitor检测到错误:
Record模式
等待开始指令
Tracealyzer
注意事项
仿真的时候出现LED不亮,LED不变化或者延迟变化。
因为仿真的时候用的是BSRR寄存器。
这个寄存器的功能是把要改变的Bit写入,由MCU去改变相应的数据。
而Qemu在仿真的时候似乎同步没有做好,就出现了以上情况
将访问BSRR的操作改为ODR,这个寄存器就是直接操作io。
具体操作比如:
Set: GPIOx->ODR |= GPIO_Pin_xx;
ReSet:GPIOx->ODR &= ~GPIO_Pin_xx;
用Tracealyzer通过SEGGER传输数据时,出现速度不稳定,甚至跳的超过100%
查看结果时,出现任务执行时长异常,长时间没有任务在工作
仿真时,将snapshot保存出来,查看时出现未找到时间标签
原因未知
在trcConfig.h的151行上方的空格添加
#define TRC_CFG_ARM_CM_USE_SYSTICK
仿真或者在板子上运行时,时间长度不正确
比如,现实生活7s相当于板子里的20s
因为FreeRTOS设置了MCU的主频,默认是62500000
而FreeRTOS的定时都是基于MCU主频,即内部是根据MCU主频来确定Tick(可以认为是一个CPU周期),而定时都是将ms转化为Tick。
比如,20s * 62500000Hz / 180000000Hz = 6.944s
修改FreeRTOSConfig.h的第98行,改为:
#define configCPU_CLOCK_HZ ( ( unsigned long ) 180000000 )
开着Traceanlyzer的流模式运行速度会变慢
原因未知
所以提供正常模式和Record模式。
开机时按住USER_BUTTON(蓝色按钮),或者按住USER_BUTTON,再按RESET(黑色按钮)可以进入Record模式
此时,会等待Traceanlyzer启动。
可能需要多点几次Start
有待改进
MCU里的时间和现实时间不一样,可能是config里CPU的主频设置错了。↑↑↑- 目前这种检查方式只能检查出错误,很大概率知道丢失了几个。但是不能知道丢失了那几个。
可以用一个O(1)读写的数据结构来存储,但是可能要占一定的空间