目录
一、传统的调试
Long time ago,嵌入式开发的调试是一个JLINK配合一个USB转TTL模块,从JLINK V9后,JLINK自带了一个虚拟串口,可以一边进行着SWD调试,一边使用这个USB转TTL接到板子的debug串口打印上看log,省掉了一个USB转TTL模块,桌子瞬间清爽了不少,接线图如下:
JLINK的配置如下:
先找到Jlink Commander,然后输入f,查看版本,版本号大于V9.0的支持虚拟串口功能。据说JLINK默认虚拟串口功能是关闭的,需要输入VCOM enable命令后开启,如上图,虚拟串口功能就打开了。要了解其他指令功能,可以输入?
我手头的俩JLINK买回来就已经开启了,查看有没有开启可以直接通过电脑的设备管理器查看串口号,下图是通过设备管理器查看到的虚拟串口COM4,带有JLink CDC UART Port字样。
说明开启成功,然后PC端可以通过串口调试助手或者Xshell、Mobaxterm等终端查看打印log了。
二、遇到的问题
目前的项目平台是STM32F103VET6 + RTthread5.0.0,ADC1采样11路外部模拟电压,采用软件触发;ADC2采样频率是2KHz,通过T2周期为500us update event触发ADC2采样IN6,4路注入通道都采样IN6,4次注入通道采集完成后触发一次注入通道中断,中断回调函数如下图:
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc) // 4 channel inject channel finish at a time, tigged by T2 overflow
{
if(hadc==(&hadc2))
{
for(uint8_t i = 0; i < ADC_INJECTED_RANK_4; i++) // ADC2_SAMP_NUM
{
FIFO[0][i] = HAL_ADCEx_InjectedGetValue(&hadc2, ADC_INJECTED_RANK_1 + i);
}
asc2_fifo_cnt++;
if(asc2_fifo_cnt >= 5)
{
asc2_fifo_cnt = 0;
}
HAL_GPIO_TogglePin(Curr_CHK_GPIO_Port, Curr_CHK_Pin);
// RT_ASSERT(RT_NULL != rb_adc2);
// rt_ringbuffer_put_force(rb_adc2, (rt_uint8_t *)&adc2_leak, sizeof(adc2_leak)); // if overflow, covering previous data
rt_sem_release(adc2_samp_sem); // send mailbox to lkgecur_det_task
__HAL_ADC_ENABLE_IT(&hadc2, ADC_IT_JEOC); // trig cycle ADC2 samp, HAL lib disable interrupt when a sample finish in
}
}
中断的上半部就如上面的中断回调一样,发送一个adc2_samp_sem 信号量,中断的下半部是单独一个线程,等待信号量,然后进行缓变剩余电流和突变剩余电流的计算,均方根、滑动窗口,其中均方根的计算有两处浮点运算,肯定很耗时间,主线程如下图:
while(1) // debug考虑这个任务的优先级,与SPI通信比较
{
// wait for a semphore, inject channel of ADC2
result = rt_sem_take(adc2_samp_sem, 1000); // wait for ADC2 4 inject channel complete, time should modify
if (result != RT_EOK) // debug 这个不至于, 错了再重新来一次,没什么大不了
{
LOG_E("take adc2_samp_sem failed!");
// goto _err_exit;
}
static uint8_t cnt = 0;
uint8_t rec_cnt = asc2_fifo_cnt;
for(rt_uint8_t i = 0; i < rec_cnt; i++)
{
for (rt_uint8_t j = 0; j < ADC2_SAMP_NUM; j++)
{
lkgecur_handle(FIFO[i][j]);
}
asc2_fifo_cnt--;
}
if(0 == cnt++%100);
{
LOG_D("rec_cnt = %d", rec_cnt);
}
考虑到此线程的优先级不是最高的,当高优先级抢占时采样的数据可能处理不过来,因此开始开了一个4个深度的环形缓冲区,通过串口每100次打印LOG_D(....),发现缓冲区整个都满了,idle task的 LED灯都不闪了,说明整个线程占用了CPU资源,还是处理不过来计算漏电流的任务,导致其他低优先级的任务无法执行。通过这里看那完犊子了,那就需要换主频更高的MCU加上带FPU功能的。但是想想一个采样周期2ms,4个深度都满了说明漏电流检测的函数lkgecur_handle执行了8ms还没执行完,虽然开方、平方运算耗时,但是8ms都执行不完有点扯淡,不太可能。
先在ADC2注入通道中断回调中反转一个IO口,看波形的周期,发现确实是2ms反转一次,说明T2触发采样中断是准确的,没有问题的,那接着找其他问题。
接着就先优化程序吧,先看看能用的招数,先把程序的优先级调整到第二高,仅次于SPI Slave通信,目前SPI没有接主机,因此此任务不影响,始终在suspend状态。
那就首先将环形缓冲区换成4个队列,然后看打印信息,依然是接近4个缓冲区都满,看来环形缓冲区耗时不是主要矛盾。继续,直接将lkgecur_handle中的开方、平方运算注释掉,查看打印log,现在好点了,使用的缓冲区在3、4个之间跳,那这有点扯淡了,计算不是最大的耗时矛盾点。
考虑了一下那就是串口LOG_D( )打印耗费时间了,毕竟他的打印不是中断方式的,而是通过阻塞方式进行的,对这种处理高速采样的线程对时间是很敏感的,这么分析LOG_D( )因该就是耗时的最大矛盾了。
将LOG_D( )打印注释掉后,观察现象,idle task 的LED可以闪烁了,这说明了系统目前是可调度的,任务安排没问题,采样处理线程也没有一直占用CPU;将注释掉的平方、开方运算还原,观察现象,LED依然能正常闪烁,说明LOG_D( )占用了太多的时间,不适合这种需要高速处理数据线程的调试。
三、解决的办法
要想高速的调试,看变量的值,只能通过Segger的RTT了,实时打印不影响系统的运行,因为Segger仿真器和MCU之间采用了共享内存的方式,log的打印是1us级别的,所以高速的中断变量的打印调试、高速数据采集线程的处理函数调试都离不开他。
想想以前用到的调试手段基本一个keil配合串口打印就搞定了,目前使用的是开发环境是RT-Studio,无法像keil那样动态显示变量的值,只能暂停后才能观察变量值,不能动态刷新,这个是RT-Studio的硬伤,比keil LOW了不是一星半点。
下面进行RTT的RTthread的配置,查找资料发现RTthread已经支持Segger-RTT,只需要简单的初始化和配置就可以代替串口打印,而不需要改变打印的函数,并且支持FinSH。
在RT-Studio里面查找RTT库,输入seg,可以搜索到SEGGER_RTT,点击添加,网上资料显示可以下载V1.1.0,但是我这里操作V1.1.0后在pakage里面无法加载源文件,版本选择了latest后可以了。
在board.c中添加RTT初始化代码:
extern int rt_hw_jlink_rtt_init(void);
rt_hw_jlink_rtt_init(); // support RTT
代码添加完后如下图:
注意一个问题,这里RTT占用了一个系统的空闲钩子函数
rt_thread_idle_sethook(segger_rtt_check);
所以如果没有使用系统的空闲钩子函数,需要打开空闲钩子函数,如果已经使用了空闲钩子函数,则钩子函数的数量要增加一个,不然会自己的一个钩子函数会得不到执行的机会,工程中原来我使用了两个空闲钩子函数,没有增加,结果LED运行灯不闪了,找到此问题后,将钩子函数增加到3,LED可以闪烁了,并且RTT打印也可以了,增加钩子函数操作如下图:
将console的名字改为:jlinkRtt,操作如下:
上面的操作等效于修改rtconfig.h中的 #define RT_CONSOLE_DEVICE_NAME "jlinkRtt"
这样,工程的SWD调试就支持Segger-RTT了,打开J-link RTTViewer 可以跟串口调试那样操作,要想输入FiSH指令,进行一下设置:
这样在下面的输入框中就可以输入FinSH指令了,到此高速调试环境都搭建完成了,可以看到,输出的缓冲区计数值为rec_cnt = 1,这里可以清晰的看到4个采样周期内线程可以很好的将数据都消费掉,说明目前的MCU可以胜任计算计算漏电流的任务。
通过这一通折腾,我觉着要是没有keil,还是使用RTT调试比较靠谱些,要是用串口打印的话,这些高速处理的中断和线程中变量没法查看,所以工程搭建的一开始最好把RTT支持调好。