如果对Simulink代码生成与电机控制感兴趣,侧重永磁无感FOC方向,可以查看链接。
本文介绍SimCoJLink工具的开发背景、使用方法以及注意事项。SimCoJLink是在Simulink环境下的一组JLink驱动模块,利用JLink调试器盒子快速读写ARM系MCU的数据,并且不占用任何额外引脚与CPU时间,下发参数和上传信号只是将数据拷贝到指定内存位置即可。JLink Pro传输速度可以高达1.3MB/s,JLink V9也最高支持400KB/s,完全能够胜任电机控制的算法调试需求。配合Simulink强大的建模能力,可以方便快速的搭建上位机测控模型,绘制信号波形,下发控制命令。 该工具只提供编译文件,不提供源代码,也不会收费。
按照载波频率20kHz上传变量,假设上传4个浮点数据,传输的带宽为 "20e3* 4* 4Byte/s = 320e3Byte/s /1024 = 312.5KB/s"
SimCoJLink库共有三个模块: SimCoJLink SET,SimCoJLink Transmit,SimCoJLink Receive。分别是对SimCoJLink进行相关设置,发送数据到MCU,读取MCU上传数据的模块。
SimCoJLink SET总共有五个参数可以设置,JLink Interface, Start Search Address, RTT Search Length, JLink Speed, JLink Target Core。
-
JLink Interface: JLink调试接口选择,根据电路的连接,选择常用的SWD或者JTAG
-
JLink RTT Block Search(搜索MCU内_SEGGER_RTT结构体地址,即MCU内交互内存)
- Start Search Address(HEX):开始搜索的起始内存地址,可以直接设置MCU的RAM起始内存地址,必须是$0x$开头的16进制。
- RTT Search Length(HEX):内存搜索的最大长度,必须是$0x$开头的16进制,最大值是$0x20000$,超过这个最大值设置会报错。
-
JLink Speed(KHz):JLink与MCU交互接口的频率,默认是50000KHz,可以不用修改,如果无法支持这个频率,JLink驱动会自动降低。最大设置80000,必须是正整数。
-
JLink Target Core: MCU的核心类型,根据MCU的数据手册选择,例如Cortex-M4,Cortex-M3,Cortex-M0等等。
SimCoJLink Receive是按照设置的采样时间去处理信号,例如设置0.1s,那么每隔0.1s将处理刷新一次数据,这种特殊的信号刷新方式需要更改Simulink示波器模块的设置为,列作为通道(基于帧)。在0.1s时间接收到的所有数据中,应该以$"TT:"$三个字符开始,后面跟上的是所有的数据,最后再加上帧尾的字符串$"ET!"$。具体的MCU端发送逻辑可参考提供的示例。
SimCoJLink Receive总共有五个参数可以设置,Frame Header, Frame Terminator, Data Type, Data Size, Block Sample Time(s)。
-
Frame Header:一个采样时间内所有数据称为一帧数据,每帧数据的帧头必须是$"TT:"$这三个字符。此参数无法更改。如果MCU发送的帧头不是此字符串,上位机模型将无法正确解析数据。
-
Frame Terminator: 每帧数据的帧尾必须是$"ET!"$这三个字符,此参数无法更改。如果MCU发送的帧尾不是这三个字符,可能收到的数据不是完整的一帧,Simulink通知栏会发出警告,但是不会报错终止模型运行。
-
Data Type: MCU上传变量的数据类型,数据类型一定要设置正确,否则无法正常解析数据。如果MCU上传的是$float$类型,选择$single$,在$Simulink$里面,$single$代表单精度浮点数,和$float$同样意义。
-
Data Size:一帧数据的数据尺寸,此参数是一个数组,前面的数字代表MCU每次发送的变量个数,后面的数字代表此模块采样周期内总共接收的数据次数。如MCU发送频率是10KHz,每次发送3个$uint16$的数据,则在采样周期0.1s时间,收到$1000$次数据,每次数据有三个变量,所以设置为数组$[3\quad 1000]$
前面的数字,变量的个数,最大值限制为20。
后面的数字,接收变量的次数,最大值限制为5000。 -
Block Sample Time(s): 模块的采样时间,也就是计算周期。此参数必须与MCU上传帧格式对应。MCU发送帧头的周期必须与这里的计算周期相同。默认设置0.1s,然后计算MCU发送的帧方式编码,并设置此模块的Data Size。
// 10KHz
void UploadParameter(void)
{
uint16_t TransBuff[10];
static uint16_t count;
uint8_t TransHeader[] = { 'T','T',':'};
uint8_t TransTerminator[] = {'E', 'T', '!'};
TransBuff[0] = Phase;
TransBuff[1] = Phase1;
TransBuff[2] = Phase2;
if( count == 0)
{
count++;
SEGGER_RTT_WriteNoLock(0, TransHeader, 3);
SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2);
}
else if( count == 999)
{
count = 0;
SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2);
SEGGER_RTT_WriteNoLock(0, TransTerminator, 3);
}
else
{
count++;
SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2);
}
}
SimCoJLink Transmit是PC机下发参数到MCU端的模块,下发的参数要求准确,所以下发参数帧末尾会加上CRC16校验码,用来校验数据,校验出错可以放弃该帧数据。 SimCoJLink Transmit 总共有两个参数,都是默认参数,不可修改。
-
Frame Header:此参数默认是$TT:$,每帧下发的参数开头三个字符都是这个帧头。
-
Block Sample Time(s):参数默认是-1,该参数设置的模块的执行周期,-1代表的是从前面输入的模块继承采样时间。前面模块的采样时间是多少,该模块采样时间和它相等。
-
发送模块执行频率不可比接收模块执行频率高!可以相等。
-
发送帧末尾的2Byte数据是校验值,只校验实际传送的变量,不包含帧头。校验方法和Modbus RTU的CRC16计算方法相同,MCU端示例代码有此校验函数代码。
-
将所提供的安装包中的JLink_x64.dll文件拷贝到MATLAB安装目录下的"\bin\win64"下,方便运行的时候查找该dll文件。
例如在作者电脑上的目录是:"C:\Program Files\Polyspace\2020b\bin\win64"。 -
将所提供安装包内的SimCoJLink_SimulinkLib文件夹添加到MATLAB的搜索目录,不可含中文。见(图)
-
打开Simulink的Library查看是否成功添加SimCoJLink库。见(图)
下载的安装包子目录SimCoJLink_Example下,存在两个测试工程实例。一个主控芯片是
STM32G474RE的ST官方开发板NUCLEO-G474RE,内核为Cortex-M4。另外一个主控芯片是
STM32G071RB的官方开发板NUCLEO-G071RB,内核为Cortex-M0。
测试流程:打开Keil工程,下载代码,RUN让程序跑起来。打开上位机Simulink模型,开始监控数据。
这两块开发板自带STLINK,需要把STLINK的SWD线与主控芯片断开,或者将STLINK电源断开,保证STLINK不会干扰SWD的调试线与JLINK连接。具体查看ST官网NUCLEO原理图。
作者实际测试电路板硬件修改情况
-
NUCLEO-G474RE : 去除R9,R10,R13,R18。JP5选5V_PWR,JLink连接CN4。USB端口供电。
-
NUCLEO-G071RB : 去除R24电阻,JP2选择CHG,USB插上供电电源,只负责供电。JLink按照线序连接CN11。
-
将作者提供包中SGEEER_MCU目录的文件,见图 ,拷入工程,并添加头文件搜索目录。
-
设置用来传输信号的RAM大小。在SEGGER_RTT_Conf.h文件95行与99行。该内存大小是MCU用来暂存发送和接收数据的内存区域,理论上是越大越好。因MCU发送到PC速度较快,占用内存相对设置大点,默认可以设置1024Byte。PC发送到MCU,速度较慢,可以设置相对小,设置三倍或者五倍实际每帧数据大小即可,默认设置128Byte。见图。
-
初始化相关的结构体与内存等。在开始发送和接收数据之前初始化。调用图中两个函数。
// 10KHz MCU发送到PC void UploadParameter(void) { uint16_t TransBuff[10]; static uint16_t count; uint8_t TransHeader[] = { 'T','T',':'}; uint8_t TransTerminator[] = {'E', 'T', '!'}; TransBuff[0] = Phase; TransBuff[1] = Phase1; TransBuff[2] = Phase2; if( count == 0) { count++; SEGGER_RTT_WriteNoLock(0, TransHeader, 3); SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2); } else if( count == 999) { count = 0; SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2); SEGGER_RTT_WriteNoLock(0, TransTerminator, 3); } else { count++; SEGGER_RTT_WriteNoLock(0, (uint8_t *)TransBuff, 3*2); } } // while循环中执行或其它低频任务中执行,MCU接收 // 读取已接收到数据字节数,大于一帧总字节数,开始处理 if(SEGGER_RTT_GetAvailReadSpace(0) > 5*2+5-1) { uint8_t RTT_RecvBuffer[15]; // 从RTT内存读取数据15字节数据到RTT_Buffer内 SEGGER_RTT_ReadNoLock( 0, RTT_RecvBuffer, 15); // 计算CRC16校验码,只对实际数据校验,不包含帧头 uint16_t CRC16_Result = usMBCRC16(RTT_RecvBuffer+3, 10); uint16_t RecvResult = (RTT_RecvBuffer[13] << 8) + RTT_RecvBuffer[14]; if(CRC16_Result == RecvResult) { // 校验通过,将5个uint16数据拷贝到memTemp数组中 memcpy(memTemp, RTT_RecvBuffer+3, 10); } }
-
MCU按照固定频率发送数据,并检查接收数据缓存大小接收数据。 如示例STM32G071中,发送接收函数见上图代码块。MCU发送到PC按照10KHz频率,每次发送3个uint16变量类型,每1000次为一帧。MCU接收PC数据,每0.1s一帧,每帧数据共有15字节,帧头"TT:"3个字节,5个uint16是10个字节,帧尾是CRC16校验码2个字节。
-
SEGGER_RTT_Init
/*************************************************************** * 函数描述: * 对RTT相关结构体与内存初始化。 * ***************************************************************/ void SEGGER_RTT_Init (void)
-
SEGGER_RTT_SetTerminal
/*************************************************************** * 函数描述: * 对RTT的终端进行设置。 * * 输入参数 * TerminalId : 终端号,SimCoJLink必须选择是0。 * * 返回值 * >=0,成功设置。 * <0 ,设置失败。 ***************************************************************/ int SEGGER_RTT_SetTerminal (unsigned char TerminalId)
-
SEGGER_RTT_WriteNoLock
/*************************************************************** * 函数描述: * 将指定字节数目的内存数据尝试拷贝到RTT上传内存区域, * 等待PC机读取到电脑端。如果拷贝不成功,不会死锁等待, * 正常退出函数。 * * 输入参数 * BufferIndex : 对于SimCoJLink工具,该参数是0。 * pBuffer : 待拷贝内存的指针。 * NumBytes : 待拷贝内存的字节数目。 * * 返回值 * 成功写入的字节数目。 * * 注意点 * 执行该函数之前,必须已调用过初始化函数, * 在本说明书4.1节已说明。 ***************************************************************/ unsigned SEGGER_RTT_WriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes)
-
SEGGER_RTT_ReadNoLock
/*************************************************************** * 函数描述: * 从PC写入到MCU的内存区域读取指定字节数目的数据。 * 如果读取不成功,不会死锁等待,正常退出函数。 * * 输入参数 * BufferIndex : 对于SimCoJLink工具,该参数是0。 * pData : 待写入的内存指针。 * BufferSizde : 读取的字节数目。 * * 返回值 * 成功读取的字节数目。 * * 注意点 * 执行该函数之前,必须已调用过初始化函数, * 在本说明书4.1节已说明。 ***************************************************************/ unsigned SEGGER_RTT_ReadNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize)
-
usMBCRC16
/*************************************************************** * 函数描述: * 计算CRC16校验码 * * 输入参数 * pucFrame : 待校验内存的指针。 * usLen : 待校验内存的字节数目。 * * 返回值 * CRC16校验码。 ***************************************************************/ unsigned short usMBCRC16(unsigned char * pucFrame, unsigned short usLen)
-
SEGGER_RTT_GetAvailReadSpace
/*************************************************************** * 函数描述: * 获取PC到MCU端的RAM内存,已经写入的字节数目。 * * 输入参数 * BufferIndex : 对于SimCoJLink工具,该参数是0。 * * 返回值 * 已经写入的字节数目。 ***************************************************************/ unsigned SEGGER_RTT_GetAvailReadSpace (unsigned BufferIndex)
-
SEGGER_RTT_GetAvailWriteSpace
/*************************************************************** * 函数描述: * 获取MCU到PC端的RAM内存,剩余可写入的字节数目。 * * 输入参数 * BufferIndex : 对于SimCoJLink工具,该参数是0。 * * 返回值 * 剩余可写入的字节数目。 * * 注意点 * 返回值是1时,说明可写入的字节为1,已经无法继续写入新数据, * 解决方法 1. 更换速度更高的JLink * 2. 扩展RAM内存配置,见本说明书4.1 * 3. 降低发送频率和字节数目 ***************************************************************/ unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex)