使用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 }
编译固件后,烧录,开机,然后使用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)
脚本运行结果:
脚本里面能处理的工作就非常多了,可以根据实际工作需要进行处理。实际上 pylink 使用的还是J-Link安装目录下的“JLinkARM.dll”文件提供的接口。如果需要使用C++或其他语言编写GUI窗口就可以加载这个动态库,可进一步自定义具有自己公司风格的日志查看、固件烧录工具等。
但是这个RTT工具也有两个不理想的地方:
- 在RTT Viewer上或者自己写的脚本内,如果目标设备重启,工具日志就不输出了。不知道怎么在代码里面实时检测,找了很久都没找到。
- 需要买一个J-Link仿真器。如果是研发就无所谓,因为下载、调试本来就需要J-Link,如果是售后或者其他的什么地方需要获取日志信息就不方便了,没有串口工具那么方便。
《完》