使用Segger RTT打印日志

在使用cortex芯片打印日志的时候,常用的方式是使用串口。现记录一种使用RTT的方式打印日志(需要去J-Link Debug Probes by SEGGER – the Embedded Experts下载并安装J-Link工具)。

RTT使用的是SWDIO、SWCLK两线接口,与J-Link下载器的接口是相同的,在工程代码里面只需要加入SEGGER_RTT.c和SEGGER_RTT_printf.c两个文件,就可以使用SEGGER_RTT_printf、SEGGER_RTT_vprintf、SEGGER_RTT_Write 等接口来打印日志了。这两个文件放在J-Link的工具目录下的“SEGGER_RTT_V766c.zip”压缩包内,我这里是“C:\Program Files\SEGGER\JLink\Samples\RTT”目录。将压缩包里面的文件取出来用就行了。

RTT打印日志的速率比其他的方式快很多,各种方式的速率对比信息可以上网查询。在 J-link的日志读写显示 -RTT调试_weixin_30730151的博客-CSDN博客 这里有速率对比介绍。

 

我在工程代码里面没有直接使用SEGGER_RTT_printf,网上说这个函数对浮点数的支持不友好,所以我就直接使用snprintf包装了一下日志接口后调用SEGGER_RTT_Write 函数来实现。

 1 void log(int level, const char* restrict format, ...) {
 2     char buffer[256];
 3     int total;
 4     va_list ap;
 5 
 6     // 根据需要对 level 进行过滤
 7     // 根据需要加入系统运行的时间戳
 8     // snprintf/vsnprintf的返回值可能大于 sizeof(buffer) 值
 9     va_start(ap, format);
10     total = vsnprintf(buffer, sizeof(buffer), format, ap);
11     va_end(ap);
12 
13     if (0 >= total) {
14         return;
15     } else if (total >= sizeof(buffer) - 3) {
16         total = sizeof(buffer) - 3;
17     }
18     buffer[total++] = '\r';
19     buffer[total++] = '\n';
20     buffer[total++] = '\0';
21     SEGGER_RTT_Write(0, buffer, total);
22 }
View Code

 

编译固件后,烧录,开机,然后使用RTT Viewer来查看日志。

 

  

后来感觉RTT Viewer不方便,对于一些需要根据运行日志做处理分析的工具而言不好用,所以写了一个python脚本。

需要电脑安装 pylink 包,不要使用pip直接安装,应该去 pylink-square · PyPI 下载 pylink 的 whl 文件到本地,再使用 pip 工具安装下载过来的 whl 文件。

# 安装pylink,从https://pypi.org/project/pylink-square/#files下载
> pip install pylink_square-0.13.0-py2.py3-none-any.whl

# 查看pylink版本
>pip list | findstr "pylink"
pylink-square    0.13.0

脚本代码:

  1 #encoding: utf-8
  2 import pylink
  3 import re
  4 import sys
  5 import os
  6 from time import sleep
  7 import signal
  8 
  9 # https://github.com/square/pylink/blob/master/pylink/structs.py
 10 # https://pylink.readthedocs.io/en/latest/pylink.html#jlock
 11 
 12 terminate_logger = False
 13 def signal_handle(signum, frame):
 14     print(f"收到信号 {signum}")
 15     print("结束程序")
 16     sys.exit(0)
 17 
 18 def run(serial:str=None, chip:str=None, filter:str=None):
 19     signal.signal(signal.SIGTERM, signal_handle)
 20     signal.signal(signal.SIGINT, signal_handle)
 21     try:
 22         jlink = pylink.JLink()
 23     except pylink.errors.JLinkException as e:
 24         print('无法获取J-Link对象')
 25         sys.exit(-1)
 26 
 27     try:
 28         emulators = jlink.connected_emulators()
 29     except pylink.errors.JLinkException as e:
 30         print('无法枚举已连接的仿真器')
 31         sys.exit(-1)
 32 
 33     if serial is None:
 34         if 0 == len(emulators):
 35             print('未找到仿真器')
 36             sys.exit(0)
 37         elif 1 == len(emulators):
 38             serial = emulators[0]
 39         else:
 40             eid = 0
 41             for e in emulators:
 42                 print(f'<{eid+1}> {emulators[eid]}')
 43                 eid = eid + 1
 44             choice = input(f'请输入仿真器序号:')
 45             if not str.isdigit(choice):
 46                 print('输入的序号有误')
 47                 sys.exit(0)
 48             eid = int(choice)
 49             if 0 >= eid or eid > len(emulators):
 50                 print('输入的序号不在范围内')
 51                 sys.exit(0)
 52             serial = emulators[eid - 1].SerialNumber
 53     else:
 54         if 0 == len(emulators):
 55             print('未连接任何仿真器')
 56             sys.exit(0)
 57         else:
 58             matched = False
 59             for e in emulators:
 60                 print(f'已发现仿真器 {e.SerialNumber}', end=' -- ')
 61                 if serial == str(e.SerialNumber):
 62                     matched = True
 63                     print("matched")
 64                 else:
 65                     print("does not match.")
 66 
 67             if not matched:
 68                 print(f'未匹配到指定的仿真器 {serial}')
 69                 sys.exit(0)
 70 
 71     # jlink.supported_device(index=0) -> JLinkDeviceInfo
 72     if chip is None:
 73         chip = input('请输入芯片名:')
 74         if 0 == len(chip):
 75             print("芯片名无效")
 76             sys.exit(0)
 77 
 78     try:
 79         cid = jlink.get_device_index(chip)
 80     except pylink.errors.JLinkException as e:
 81         print(f'获取 {chip} 信息失败')
 82         sys.exit(-1)
 83     else:
 84         if 0 > cid:
 85             print(f'不支持 {chip}')
 86             sys.exit(0)
 87     
 88     try:
 89         jlink.open(serial_no=serial)
 90     except pylink.errors.JLinkException as e:
 91         print('打开仿真器时出错')
 92         print(e)
 93         sys.exit(0)
 94     except TypeError as e:
 95         print('打开仿真器时出错')
 96         print(e)
 97         sys.exit(0)
 98     except AttributeError as e:
 99         print('打开仿真器时出错')
100         print(e)
101         sys.exit(0)
102     except ValueError as e:
103         print('打开仿真器时出错')
104         print(e)
105         sys.exit(0)
106     
107     jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
108     try:
109         jlink.connect(chip_name=chip)
110     except pylink.errors.JLinkException as e:
111         print('连接仿真器时出错')
112         print(e)
113         jlink.close()
114         sys.exit(0)
115     except TypeError as e:
116         print('连接仿真器时出错')
117         print(e)
118         jlink.close()
119         sys.exit(0)
120 
121     # print(jlink.serial_number)
122     print(jlink.product_name)
123     try:
124         jlink.rtt_start()
125     except pylink.errors.JLinkRTTException as e:
126         print('启动仿真器时出错')
127         jlink.close()
128         sys.exit(0)
129 
130     # 测试仿真器
131     if not jlink.test():
132         print('仿真器工作异常')
133         sys.exit(0)
134 
135     # todo: 如果连接过程中,目标芯片重启/断电,pylink是没有任何通知的,也没有任何查询接口。
136     # jlink.rtt_write(0, [ord(x) for x in list(writedata)])
137     maxCharactorsCountPerLine = 256
138     sleepSecond = 0.01
139     if filter is None or 0 == len(filter.strip()):
140         while not terminate_logger:
141             try:
142                 bl = jlink.rtt_read(0, maxCharactorsCountPerLine) # return a list of bytes
143             except pylink.errors.JLinkRTTException as e:
144                 print(e)
145             else:
146                 if 0 < len(bl):
147                     text = bytes(bl).decode(errors='ignore')
148                     # 判断本地读取的数据最后一个字符是否为换行符
149                     if text[-1] == '\n':
150                         print(text)
151                     else:
152                         print(text, end="")
153                 else:
154                     if not jlink.connected():
155                         print('\n\n--仿真器已断开--\n')
156                         sys.exit(0)
157                     if not jlink.target_connected():
158                         print('\n\n--目标器件已断开--\n')
159                         sys.exit(0)
160                     # 休眠一会儿
161                     sleep(sleepSecond)
162     else:
163         cachedLine = None
164         while not terminate_logger:
165             try:
166                 bl = jlink.rtt_read(0, maxCharactorsCountPerLine) # return a list of bytes
167             except pylink.errors.JLinkRTTException as e:
168                 print(e)
169             except ValueError as e:
170                 print(e)
171             else:
172                 if 0 < len(bl):
173                     # 串接上一次未处理的内容并按照 \n 分隔成多行
174                     if cachedLine is None:
175                         text = bytes(bl).decode(errors='ignore')
176                     else:
177                         text = cachedLine + bytes(bl).decode()
178 
179                     strlines = text.splitlines()
180                     # 判断本地读取的数据最后一个字符是否为换行符
181                     if text[-1] == '\n':
182                         cachedLine = None
183                     else:
184                         cachedLine = strlines[-1]
185 
186                     # 逐行匹配过滤并打印
187                     for s in strlines:
188                         if 0 < len(re.findall(filter, s)):
189                             print(s)
190                 else:
191                     if not jlink.connected():
192                         print('\n\n--仿真器已断开--\n')
193                         sys.exit(0)
194                     if not jlink.target_connected():
195                         print('\n\n--目标器件已断开--\n')
196                         sys.exit(0)
197                     # 休眠一会儿
198                     sleep(sleepSecond)
199 
200     try:
201         jlink.rtt_stop()
202     except pylink.errors.JLinkRTTException as e:
203         print('停止仿真器时出错')
204         jlink.close()
205         sys.exit(0)
206 
207     try:
208         jlink.close()
209     except pylink.errors.JLinkException as e:
210         print('关闭仿真器时出错')
211         print(e)
212 
213     sys.exit(0)
214 
215 # rttview.py --chip='EFM32PG12BXXXF1024' --serial='59607172' --filter="batter|charger"
216 def usage(name:str):
217     print(f'\nUsage:')
218     print(f'\t{name} [--chip=<name>] [--serial=<SN>] [--filter=<keywold>]\n')
219     print(f'\nExample:')
220     print(f'\t{name} --chip="EFM32PG12BXXXF1024" --serial="59607172" --filter="error|keyword"\n')
221 
222 def argstrtrim(s:str) -> str:
223     s = s.strip()
224     if s.startswith('"') or s.startswith("'"):
225         s = s[1:]
226     if s.endswith('"') or s.endswith("'"):
227         s = s[:-1]
228     return s.strip()
229 
230 if __name__=="__main__":
231     serial = None
232     chip = None
233     keyword = None
234     if 1 < len(sys.argv):
235         for arg in sys.argv[1:]:
236             if arg.startswith("--chip="):
237                 chip = argstrtrim(arg[len("--chip="):])
238                 print(f'chip={chip}')
239             elif arg.startswith("--serial="):
240                 serial = argstrtrim(arg[len("--serial="):])
241                 print(f'serial={serial}')
242             elif arg.startswith("--filter="):
243                 keyword = argstrtrim(arg[len("--filter="):])
244                 print(f'keyword={keyword}')
245             else:
246                 print(f'不支持的参数项 {arg}')
247                 usage(os.path.basename(sys.argv[0]))
248                 sys.exit(0)
249 
250     # run(serial='59607172', chip='EFM32PG12BXXXF1024', filter='battery|wifi')
251     # run(serial='59607172', chip='EFM32PG12BXXXF1024')
252     # run(chip='EFM32PG12BXXXF1024')
253     run(serial=serial, chip=chip, filter=keyword)
View Code

脚本运行结果:

 

 

 

脚本里面能处理的工作就非常多了,可以根据实际工作需要进行处理。实际上 pylink 使用的还是J-Link安装目录下的“JLinkARM.dll”文件提供的接口。如果需要使用C++或其他语言编写GUI窗口就可以加载这个动态库,可进一步自定义具有自己公司风格的日志查看、固件烧录工具等。

但是这个RTT工具也有两个不理想的地方:

  1. 在RTT Viewer上或者自己写的脚本内,如果目标设备重启,工具日志就不输出了。不知道怎么在代码里面实时检测,找了很久都没找到。
  2. 需要买一个J-Link仿真器。如果是研发就无所谓,因为下载、调试本来就需要J-Link,如果是售后或者其他的什么地方需要获取日志信息就不方便了,没有串口工具那么方便。

 

《完》

 

posted @ 2022-06-28 11:24  -ssdq-  阅读(1560)  评论(0编辑  收藏  举报