MAXQ微控制器提供的JTAG引导加载程序允许外部JTAG主站使用一组标准化命令轻松识别和编程任何MAXQ微控制器。本应用笔记中包含的代码可用作构建功能齐全的JTAG引导加载程序主应用程序的起点。主控应用可以识别、初始化、加载和验证任何支持标准化引导加载程序命令集的MAXQ微控制器的代码和数据存储器内容。
概述
MAXQ微控制器包括可重写的板载程序存储器,通常包括一个基于ROM的引导加载程序,该引导加载程序存储器可以使用微控制器的JTAG兼容调试端口加载。尽管JTAG引导加载程序提供的确切功能因设备而异,但它通常包括允许读取、写入、验证和擦除程序和数据存储器的命令。一些器件提供引导加载程序的替代接口(如串行端口或SPI™接口),但JTAG接口最常使用有两个原因。首先,JTAG接口必须已经存在,以支持在线调试功能。其次,JTAG接口通常不被最终用户应用程序使用(与串行端口不同)。加载程序代码后,可以使用可选的密码机制来限制对引导加载程序或在线调试功能的访问。有关每个MAXQ器件所支持功能的详细信息,请参阅产品特定信息,包括数据资料和用户指南。
本应用笔记介绍了为MAXQ2000实现JTAG引导加载程序主机所需的基本步骤。这些步骤包括与JTAG端口接口,与测试访问端口(TAP)控制器通信,激活引导加载程序模式以及向基于ROM的引导加载程序发送命令。由于JTAG端口在所有MAXQ器件上工作原理相同,并且MAXQ自举加载程序使用共享命令集,因此本应用笔记中介绍的大部分主题(以及大多数示例代码)将适用于为任何MAXQ微控制器实现JTAG引导加载程序主机。
除串行端口外,MAXQ2000没有特殊功能用于此实现。这意味着本文给出的示例代码可以很容易地重新定位,以运行在任何具有足够程序存储器的MAXQ20器件上。代码是用MAXQ汇编编写的,并使用MAX-IDE开发环境进行编译。该代码可供下载。
硬件设置
本应用笔记的示例代码是利用一对MAXQ2000评估(EV)板开发的。需要两个MAXQ2000评估板来执行本文所述的软件。一个MAXQ2000(JTAG主站)运行示例代码;第二个MAXQ2000作为JTAG从站,由主站重新编程。两个MAXQ8微控制器均采用标准00.2000MHz晶体。
主用MAXQ2000评估板经过修改,在原型设计区安装一个2 x 5 引脚接头,提供JTAG电缆连接的主端。接头上的引脚遵循标准的MAXQ JTAG接头布局,如表1所示进行连接。
JTAG针座引脚 | JTAG电缆功能 |
MAXQ2000 JTAG 主连接 |
MAXQ2000 JTAG 从机连接 |
1 | TCK (测试时钟) | P0.0 (输出) | P4.0 (输入) |
2 | 接地 | 接地 | 接地 |
3 | TDO(测试数据输出) | P0.1 (输入) | P4.3 (输出) |
4 | V裁判 | – | – |
5 | TMS(测试模式选择) | P0.2 (输出) | P4.2 (输入) |
6 | n重置 | P0.4 (开漏) | n复位 (输入) |
7 | 键控销 | – | – |
8 | +5V | – | – |
9 | TDI (测试数据输入) | P0.3 (输出) | P4.1 (输入) |
10 | 接地 | 接地 | 接地 |
从MAXQ2000评估板无需修改。如上所述,2 x 5 JTAG接头安装并连接在主MAXQ2000评估板的原型设计区域。然后连接两个评估板;在主评估板原型区域的2 x 5 JTAG接头和从评估板上的标准JTAG接头(J2)之间连接了标准的5 x 4 JTAG电缆(通常用于将串行转JTAG板连接到MAXQ评估板的JTAG类型)。该JTAG 2 x 5连接器包含在MAXQ2000评估板中。
为简化起见,没有尝试通过JTAG电缆将电源或基准电压从主评估板连接到从评估板。相反,两个评估板设置为以相同的V工作。迪奥电压(约3.6V)。这种设置确保MAXQ2000的主从器件在公共I/O轨电平下工作。
从评估板还将LCD子板(MAXQ2000-K01)安装在接头J3上。表2列出了两块板(以及串行转JTAG板)的跳线和DIP开关设置。注意:所有未列出的跳线都应断开连接。图 1 显示了最终设置。
板 | 开关或跳线 | 设置 | 笔记 |
串行转JTAG板 | JH1 | 连接 | |
JH2 | 连接 | ||
JH3 | 连接 | 通过JTAG电缆提供5V电源 | |
主MAXQ2000评估板 | JU1 | 引脚 1 和 2 已连接 | 权力五DD采用 2.5V 电源 |
JU2 | 引脚 1 和 2 已连接 | 权力五迪奥采用 3.6V 电源 | |
JU3 | 引脚 1 和 2 已连接 | 权力五液晶显示器采用 3.6V 电源 | |
JU4 | 连接 | 采用JTAG 5V电源为套件板供电 | |
DIP SW1 | 开关 #4 和 #8 打开;所有其他开关关闭 | 使串行端口 0 输出到 J5 | |
DIP SW3 | 所有开关关闭 | ||
DIP SW6 | 所有开关关闭 | ||
从MAXQ2000评估板 | JU1 | Powers VDD from 2.5V supply | |
JU2 | 引脚 1 和 2 已连接 | Powers VDDIO from 3.6V supply | |
JU3 | 引脚 1 和 2 已连接 | Powers VLCD from 3.6V supply | |
DIP SW1 | 所有开关关闭 | ||
DIP SW3 | 所有开关关闭 | ||
DIP SW6 | 所有开关关闭 |
图1.JTAG演示设置。
MAXQ JTAG接口
MAXQ微控制器上的JTAG接口由四条信号线组成,用于将信息移入和移出测试访问端口(TAP)控制器。该TAP控制器提供对MAXQ自举加载程序和在线调试功能的访问。(请注意,实现调试主机虽然类似于实现引导加载程序主机,但超出了本应用笔记的范围。四条JTAG信号线如表3所示。
JTAG信号 | 信号名称 | 方向(主) | 方向(从属) | 信号描述 |
TMS | 测试模式选择 | 输出 | 输入 | 该信号线与TCK线一起用于将TAP控制器从一种工作状态切换到另一种工作状态。 |
TCK | 测试时钟 | 输出 | 输入 | 该信号为JTAG接口提供时钟。JTAG时钟限制为从机时钟频率除以8的最大值。例如,如果从机以8MHz的时钟频率运行,则TCK的JTAG时钟不能运行得超过1MHz。 |
TDI | 测试数据 | 输出 | 输入 | 该信号携带从主站发送到从站的数据。 |
统计局 | 测试数据输出 | 输入 | 输出 | 该信号将从从站发送回主站的数据传输回主站。 |
从技术上讲,nRESET引脚不是JTAG接口的一部分。它包含在JTAG电缆上,允许JTAG主机复位从机微控制器。复位从机微控制器是进入引导加载程序模式的必要步骤,如果JTAG通信意外中断,复位也很有用。
由于JTAG接口是全双工的,因此数据在TDI线路上从主站转移到从机的同时,数据在TDO线路上从从机转移到主站。从机在TCK的上升沿对输入数据(在TDI和TMS上)进行采样,并将传出数据驱动到TCK下降沿TDO上的主站。对于传入和传出数据,值首先以最低有效位传输。
本应用笔记仅简要概述JTAG接口和TAP控制器,以解释示例代码的操作。有关这些特性的更详细讨论,请参考MAXQ系列用户指南的测试访问端口(TAP)、在线调试模式和在系统编程部分。
与TAP控制器通信
TAP控制器围绕状态机构建,如下图2所示。TAP 状态机中包含 <> 个离散状态。根据TMS信号的值,从一种状态到另一种状态的转换发生在TCK的每个上升沿上。例如,如果TAP控制器处于选择DR扫描状态,并且TCK上出现上升沿:
如果TMS = 1,TAP控制器将转换为选择红外扫描状态。
如果 TMS = 0,TAP 控制器将转换为捕获 DR 状态。
通过这种方式,可以将TAP控制器时钟设置为任何所需状态。关于下面的状态图(图 2),有两点需要注意:
无论启动状态如何,五个“1”转换(保持TMS高电平和时钟TCK五个完整周期)将始终使状态机返回到测试-逻辑-复位状态。这意味着,如果TAP控制器的当前状态未知,或者JTAG主站和从站之间的通信以某种方式中断,则始终可以通过时钟进行五个“1”转换,使TAP控制器恢复到已知状态。
即使TCK时钟继续运行,也可以无限期地暂停JTAG通信并保持运行-测试-空闲、暂停-DR或暂停-IR状态,而不会影响TAP控制器的状态。
图2.测试访问端口 (TAP) 状态机。
TAP控制器的状态机提供对两个控制寄存器的访问,这两个控制寄存器又提供引导加载程序的接口、调试接口和其他功能。
IR(指令寄存器)的宽度始终为三位。该寄存器充当索引寄存器,进而控制DR的功能(见下文)。
DR(数据寄存器)是TAP控制器内多个寄存器之一的接入点。当位移入或移出DR时,实际访问的寄存器取决于IR的当前值。
图3.在 TAP 控制器中注册访问权限。
如图3所示(DR指向三个内部寄存器之一,具体取决于IR的值。
如果 IR = 011b,则 TAP 控制器处于旁路模式。在此模式下(这是 TAP 控制器的默认模式),转移到 DR(通过 TDI)的数据只是通过 TDO 再次移出。通过TAP控制器传输的数据不会改变内部寄存器。
设置 IR = 100b 会将 TAP 控制器置于系统编程模式。在此模式下,移入DR的数据将移入3位系统编程寄存器。该寄存器(也可由MAXQ微存储器作为ICDF寄存器的位[3:1]访问)控制复位后MAXQ是进入正常程序执行模式还是引导加载程序模式。如果启用了引导加载程序模式,它还控制引导加载程序将使用哪个接口(JTAG、串行或SPI)。
设置 IR = 010b 会将 TAP 控制器置于调试模式。在这种模式下,移入DR的数据被移入内部10位调试寄存器,引导加载程序可以读取该寄存器。引导加载程序输出的数据也通过此寄存器移出(以及两个状态位),并通过TDO从那里移出。该寄存器用于在引导加载程序和在线调试模式下传输数据。
在上面讨论的三种模式中,旁路模式是“无操作”模式;它不提供对我们感兴趣的引导加载程序函数的访问。系统编程模式,尽管它的名字,实际上并不用于与引导加载程序通信,只是为了启用对它的访问。一旦引导加载程序处于活动状态并正在通信,此 TAP 模式将不再有用。Debug 模式允许访问 10 位输入/输出寄存器,是用于执行与引导加载程序的所有通信的模式。启用引导加载程序后,TAP 控制器的此模式将独占使用,直到加载程序会话完成。
控制JTAG端口和复位线
从机MAXQ2000上的JTAG/TAP端口的四条线(TMS、TCK、TDO和TDI)和nRESET线分别连接到主站MAXQ2000上的一个端口引脚。控制JTAG接口的第一步是正确配置这些线路。
#define TCK PO0.0 ; Test Clock - Master output #define TDO PI0.1 ; Test Data Out - Slave output, master input #define TMS PO0.2 ; Test Mode Sel - Master output #define TDI PO0.3 ; Test Data In - Master output #define RST PD0.4 ; Reset - Master open-drain output (on 1)
四条JTAG生产线在标准驱动模式下运行。从主机的角度来看,TMS、TCK和TDI将始终作为输出驱动,而TDO(由从机驱动)将始终作为输入驱动。JTAG线的方向是固定的,而不是双向的。nRESET线路是一种特殊情况,配置为主端的漏极开路输出。通常,从机会将自己的nRESET线拉高,因此主机应仅将线拉下(当从机复位时)或完全释放(在所有其他时间)。主机没有理由显式驱动从机的nRESET线高电平。
;============================================================================== ;= ;= initializeJTAG ;= ;= Sets up the port pins for the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : None ;= initializeJTAG: move PD0.0, #1 ; TCK - master output move PO0.0, #1 ; Drive high move PD0.1, #0 ; TDO - master input move PO0.1, #1 ; Weak pullup on move PD0.2, #1 ; TMS - master output move PO0.2, #1 ; Drive low move PD0.3, #1 ; TDI - master output move PO0.3, #1 ; Drive high move PD0.4, #0 ; RST - open drain when 1, tristate when 0 move PO0.4, #0 ; Weak pullup off ret
初始化端口引脚后,时钟0和时钟1例程用于时钟TMS线上的静态0和1,以将TAP控制器从一个状态推进到下一个状态。JTAG时钟可以以任何频率驱动,只要JTAG时钟速率保持在从机微控制器系统时钟速率的1/8最大值以下即可。这里不需要考虑主站的系统时钟速率;它不需要以任何方式与从站的系统时钟速率匹配。主站可以比从站运行得更快或更慢,而不会引起JTAG通信问题。
由于本例中的从机MAXQ2000安装了8MHz时钟晶体,因此主机可以毫无问题地驱动高达1MHz的JTAG时钟。在本例中,100kHz的JTAG时钟速率就足够了。
#define JCLOCK 40 ; 100kHz : (((10us / (1/8MHz)) / 2) ;============================================================================== ;= ;= clock0 ;= ;= Clocks a zero TMS bit into the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC[0] clock0: move TMS, #0 ; Drive TMS low move LC[0], #JCLOCK djnz LC[0], $ move TCK, #1 ; Clock rising edge move LC[0], #JCLOCK djnz LC[0], $ move TCK, #0 ; Clock falling edge move LC[0], #JCLOCK djnz LC[0], $ ret ;============================================================================== ;= ;= clock1 ;= ;= Clocks a one TMS bit into the JTAG interface. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC[0] clock1: move TMS, #1 ; Drive TMS high move LC[0], #JCLOCK djnz LC[0], $ move TCK, #1 ; Clock rising edge move LC[0], #JCLOCK djnz LC[0], $ move TCK, #0 ; Clock falling edge move LC[0], #JCLOCK djnz LC[0], $ ret
有了这两个例程,我们可以添加一个例程来初始化TAP控制器,方法是强制TAP控制器返回到测试逻辑复位状态。请注意,测试逻辑重置状态(顾名思义)执行 TAP 逻辑的完全重置,包括确定引导加载程序是否启用以及引导加载程序正在使用哪个接口的位(SPE 和 PSS[1:0])。因此,一旦进入引导加载程序模式,将TAP控制器设置为测试-逻辑-复位将重置设备并退出引导加载程序模式。演示应用程序中使用的JTAG例程(testLogicReset本身除外)都假定TAP控制器在例程开始时处于运行-测试-空闲状态。在JTAG通信例程中,TAP控制器将切换各种状态;在例程结束时,TAP 控制器将始终返回到运行-测试-空闲状态。
;============================================================================== ;= ;= testLogicReset ;= clock0, clock1 ;= ;= Resets the JTAG/TAP controller to its starting state. ;= ;= Inputs : None ;= Outputs : None ;= Destroys : LC[0] ;= testLogicReset: call clock1 call clock1 call clock1 call clock1 call clock1 call clock1 call clock1 call clock0 ; Brings us to Run-Test-Idle ret
写入TAP指令寄存器
将值加载到 TAP 控制器的 IR 中是通过遍历 TAP 状态机向下移动到 Shift-IR 状态并输入新的 3 位值来完成的。在输入 Update-IR 状态之前,该值实际上不会从移位寄存器复制到指令寄存器。
与所有JTAG移位寄存器操作一样,随着新值的移入,寄存器的当前内容被移出(通过TDO)。但是,移出的值将始终是固定的(001b);这是JTAG标准强制要求的,用于测试JTAG接口的功能。
移入和移出任一移位寄存器(IR或DR)的过程是相同的;TAP 状态机必须分别处于 Shift-IR 或 Shift-DR 状态。输入位(TDI处)由TAP控制器在每个TCK周期的上升沿采样,而输出位(TDO)在TCK周期的下降沿上被驱动。
;============================================================================== ;= ;= shift ;= ;= In a shift register state, clocks in a TDI bit and clocks out a TDO bit. ;= ;= Inputs : C - Bit to shift in to TDI. ;= Outputs : C - Bit shifted out from TDO. ;= Destroys : PSW, LC[0] ;= shift: jump C, shift_bit1 shift_bit0: move TDI, #0 ; Shift in zero bit jump shift_bitEnd shift_bit1: move TDI, #1 ; Shift in one bit jump shift_bitEnd shift_bitEnd: move LC[0], #JCLOCK djnz LC[0], $ move TCK, #1 ; Rising edge, TDI is sampled move LC[0], #JCLOCK djnz LC[0], $ move TCK, #0 ; Falling edge, TDO is driven out move LC[0], #JCLOCK djnz LC[0], $ move C, TDO ; Latch TDO value ret
对于传输中的每个位,除最后一位外,TMS必须保持低电平。这一点很重要,这样,当每个位移入和移出时,状态机将保持 Shift-IR 或 Shift-DR 状态。在最后一个位周期,TMS必须被驱动为高电平,以便在最后一个位输入和输出时,TAP控制器将前进到Exit1-DR或Exit1-IR状态。
例如,在表4中,JTAG主站将值100b(系统编程模式)移入IR寄存器。IR 寄存器开始编程为旁路值 011b;TAP 控制器以“运行-测试-空闲”状态启动。如下图 4 所示,这些位从最低有效位开始,以最高有效位结束,移入(移出)TAP 控制器。因此,在第一个移位周期中,新值的位 0 移入,旧值的位 0 移出。
TCK | TMS | TDI | TDO | TAP State | 移位寄存器 | 指令寄存器 | ||||
位 2 | 位 1 | 位 0 | 位 2 | 位 1 | 位 0 | |||||
0 | 1 | x | x | Run-Test-Idle | x | x | x | 0 | 1 | 1 |
1 | 1 | x | x | Select-DR-Scan | x | x | x | 0 | 1 | 1 |
0 | 1 | x | x | Select-DR-Scan | x | x | x | 0 | 1 | 1 |
1 | 1 | x | x | Select-IR-Scan | x | x | x | 0 | 1 | 1 |
0 | 0 | x | x | Select-IR-Scan | x | x | x | 0 | 1 | 1 |
1 | 0 | x | x | Capture-IR | 0 | 0 | 1 | 0 | 1 | 1 |
0 | 0 | x | x | Capture-IR | 0 | 0 | 1 | 0 | 1 | 1 |
1 | 0 | x | x | Shift-IR | 0 | 0 | 1 | 0 | 1 | 1 |
0 | 0 | 0 | x | Shift-IR | 0 | 0 | 1 | 0 | 1 | 1 |
1 | 0 | 0 | x | Shift-IR | 0 | 0 | 1 | 0 | 1 | 1 |
0 | 0 | 0 | 1 | Shift-IR | 0 | 0 | 0 | 0 | 1 | 1 |
1 | 0 | 0 | 1 | Shift-IR | 0 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | Shift-IR | 0 | 0 | 0 | 0 | 1 | 1 |
1 | 1 | 1 | 0 | Exit1-IR | 0 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | x | 0 | Exit1-IR | 1 | 0 | 0 | 0 | 1 | 1 |
1 | 1 | x | 0 | Update-IR | 1 | 0 | 0 | 1 | 0 | 0 |
0 | 0 | x | x | Update-IR | 1 | 0 | 0 | 1 | 0 | 0 |
1 | 0 | x | x | Run-Test-Idle | x | x | x | 1 | 0 | 0 |
用于执行此操作的例程 shiftIR3 如下所示。
;============================================================================== ;= ;= shiftIR3 ;= clock0, clock1, shift ;= ;= Shifts a 3-bit value into the IR register. ;= ;= Inputs : A[0] - Low three bits contain value to shift into IR ;= Outputs : None ;= Destroys : AP, APC, A[0], PSW, LC[0] ;= shiftIR3: move APC, #80h ; Acc => A[0], turn off auto inc/dec call clock1 ; (Select DR Scan) call clock1 ; (Select IR Scan) call clock0 ; (Capture IR - loads 001b to shift register) call clock0 ; (Shift IR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxx210 c = s rrc ; sxxxxx21 c = 0 call shift ; Shift in IR bit 0 rrc ; ssxxxxx2 c = 1 call shift ; Shift in IR bit 1 rrc ; sssxxxxx c = 2 move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in IR bit 2 (Exit1 IR) call clock1 ; (Update IR) call clock0 ; (Run Test Idle) ret
写入TAP数据寄存器
将值移入和移出TAP控制器的DR的方式与IR的加载/卸载类似。通常,仅当 IR 设置为以下两个值之一时,才会执行此操作:100b(将 TAP 控制器置于系统编程模式)或 010b(将 TAP 控制器置于调试模式)。
当系统编程模式处于活动状态时,加载和卸载 DR 寄存器的操作如下。
进出DR寄存器的移位宽度为3位。所有三位都表示有效数据。
当达到 Update-DR 状态时,传输到 DR 寄存器的值将复制到从属微控制器的内部系统编程寄存器中。这三个位的使用方式如下。
从机微控制器可访问(读/写)位0作为寄存器位ICDF.1(SPE),也称为系统程序使能位。复位后由实用程序ROM检查此位,以确定是否应进入引导加载程序模式(SPE = 1)或正常程序执行模式(SPE = 0)。
位 1 和 2 可由从机微控制器作为寄存器位 ICDF.2-3 (PSS0-PSS1) 访问(读/写),也称为编程源选择位。在支持多个引导加载程序接口的微控制器上,如MAXQ2000,这些位用于选择当SPE = 1时激活哪个引导加载程序接口。当SPE = 0(正常程序执行模式)时,这些位的设置不起作用。
从 DR 寄存器传出的值是系统编程寄存器的先前值(进入 Capture-DR 状态时锁存到移位寄存器中)。
当调试模式处于活动状态时,加载和卸载 DR 寄存器的操作如下。
进出DR寄存器的移入宽度为10位。对于移出的数据,所有 10 位表示有效数据(<> 个数据位和 <> 个状态位)。对于移入的数据,仅使用八个数据位的值;不使用这两个状态位。
然后,作为引导加载程序命令的一部分,将移入 DR 寄存器的值的高八位被卸载并由引导加载程序(从实用程序 ROM 运行)读取。
从 DR 寄存器移出的 10 位值由引导加载程序加载的 8 位值(作为命令输出的一部分)和 TAP 控制器设置的两位状态信息组成。
;============================================================================== ;= ;= shiftDR3 ;= ;= Shifts a 3-bit value into the DR register. This operation should only be ;= performed when IR =100b (System Programming Mode). ;= ;= Inputs : A[0] - Low 3 bits contain value to shift into SPB (PSS1:PSS0:SPE) ;= Outputs : None ;= Destroys : AP, APC, A[0], PSW, LC[0] shiftDR3: move APC, #80h ; Acc => A[0], turn off auto inc/dec call clock1 ; (Select DR Scan) call clock0 ; (Capture DR) call clock0 ; (Shift DR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxx210 c = s rrc ; sxxxxx21 c = 0 call shift ; Shift in DR bit 0 rrc ; ssxxxxx2 c = 1 call shift ; Shift in DR bit 1 rrc ; sssxxxxx c = 2 move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in DR bit 2 (Exit1 DR) call clock1 ; (Update DR) call clock0 ; (Run Test Idle) ret ;============================================================================== ;= ;= shiftDR ;= clock0, clock1, shift ;= ;= Shifts a 10-bit value into and out of the DR register. This operation ;= should only be performed when IR = 010b (Debug/Loader Mode). ;= ;= Inputs : A[0] - Byte value (input) to shift into DR ;= Outputs : A[0] - Byte value (output) shifted out of DR ;= A[1] - Low two bits are status bits 1:0 shifted out of DR ;= A[15] - Byte value shifted in, cached for use by shiftDR_next ;= Destroys : AP, APC, PSW, LC[0] shiftDR: move APC, #80h ; Acc => A[0], turn off auto inc/dec move A[15], A[0] ; Cache input byte value for use by shiftDR_next sla2 ; Add two empty bits (for status) call clock1 ; (Select DR Scan) call clock0 ; (Capture DR) call clock0 ; (Shift DR) move TMS, #0 ; Drive TMS low move C, TDO ; xxxxxxxx76543210 c = s rrc call shift ; Shift in DR bit 0 rrc call shift ; Shift in DR bit 1 rrc call shift ; Shift in DR bit 2 rrc call shift ; Shift in DR bit 3 rrc call shift ; Shift in DR bit 4 rrc call shift ; Shift in DR bit 5 rrc call shift ; Shift in DR bit 6 rrc call shift ; Shift in DR bit 7 rrc call shift ; Shift in DR bit 8 rrc move TMS, #1 ; Drive TMS high for last bit call shift ; Shift in DR bit 9 (Exit1 DR) call clock1 ; (Update DR) call clock0 ; (Run Test Idle) push Acc ; sddd dddd 10xx xxxx sra4 ; ssss sddd dddd 10xx sra2 ; ssss sssd dddd dd10 and #0003h ; ---- ---- ---- --10 move A[1], Acc ; Return status bits only in A[1] pop Acc and #0FF00h xch ; Return data bits only in A[0] ret
进入JTAG引导加载程序模式
将MAXQ2000置于JTAG引导加载程序模式需要以下步骤。
初始化 TAP 控制器,将其重置为测试-逻辑-复位状态。
将指令寄存器 (IR) 设置为 100b 以启用系统编程模式。
将数据寄存器 (DR) 设置为 001b。这将 SPE(系统编程启用)位设置为 1 以启用引导加载程序,并将 PSS[1:0](编程源选择)位设置为 00b 以选择 JTAG 接口。
保持低电平复位可复位MAXQ2000。
释放重置。这导致MAXQ2000向矢量到实用程序ROM (8000h)中的标准复位点。然后,实用程序ROM代码将检查SPE和PSS位的值,并相应地激活JTAG引导加载程序。此时,引导加载程序正在运行并准备接受JTAG命令。
将指令寄存器 (IR) 设置为 010b 以启用调试模式。这是用于与JTAG引导加载程序或调试引擎通信的模式;在这种情况下,我们将使用此模式与引导加载程序进行通信。
通过DR移动10位值,开始向JTAG引导加载程序发送命令。
#define IR_DEBUG 010b ; Debug Mode #define IR_BYPASS 011b ; Bypass Mode (default) #define IR_SYSTEM_PROG 100b ; System Programming Mode (activate loader) ; System Programming Register settings #define SP_EXECUTE 000b ; Bootloader disabled #define SP_LOAD_JTAG 001b ; Activate JTAG bootloader #define SP_LOAD_UART 011b ; Activate UART bootloader #define SP_LOAD_SPI 101b ; Activate SPI bootloader (invalid on 2000) #define SP_RESERVED 111b ; Reserved value ... call initializeJTAG ; Set up port pins for JTAG call testLogicReset ; Reset JTAG port (ending state: Run-Test-Idle) move Acc, #IR_SYSTEM_PROG call shiftIR3 ; Load the System Programming instruction into IR move Acc, #SP_LOAD_JTAG call shiftDR3 ; Enable the bootloader in JTAG interface mode move RST, #1 ; Drive nRESET low move Acc, #100 ; Delay for 100ms call delayMS call clock0 ; Remain in Run-Test-Idle move RST, #0 ; Release nRESET move Acc, #100 ; Delay for 100ms call delayMS move Acc, #IR_DEBUG call shiftIR3 ; Enable access to the 10-bit debug shift register ;;;; Bootloader commands may now be shifted through the DR register call waitForPrompt ; Verify that the bootloader is responding ...
与引导加载程序通信
引导加载程序运行后,实用程序 ROM 中的引导加载程序代码将从从 DR 移位寄存器加载的内部寄存器读取命令代码。实用程序ROM还将结果数据写入另一个内部寄存器,该寄存器可由JTAG主站通过DR寄存器移出。这样,引导加载程序代码和JTAG主站就可以交换信息。
但是,引导加载程序代码和JTAG主站并不同步。只要JTAG时钟的速率保持在MAXQ系统时钟的最大值1/8以下,两个时钟的确切值对于通信目的无关紧要,因为JTAG是一个同步接口。但是,MAXQ系统时钟速率和收到的引导加载程序命令的性质将决定引导加载程序接收命令和发送回复之间的延迟时间。例如,工作在2000MHz的MAXQ1比工作在2000MHz的MAXQ10需要更长的时间来响应给定的引导加载程序命令,即使两个微控制器都可以使用100kHz的JTAG时钟进行通信。仅读取和返回信息的命令(例如返回ROM横幅ID或从程序存储器读取的命令)也比包含诸如编程闪存等操作的命令花费更少的时间。
通过JTAG接口传输的数据不缓冲。如果在读取上一个命令之前发送另一个 10 位命令,则前一个命令的结果将丢失。ROM代码和TAP控制器确保在JTAG主站卸载先前的数据之前,引导加载程序不会发送额外的数据,但也需要反向同步。JTAG主站需要一种方法来判断它转移到DR中的前一个字节是否已被引导加载程序读取。通过这种方式,JTAG主站可以知道何时发送下一个字节。知道这一点的方法源自 TAP 控制器通过引导加载程序的每个 8 位字节输出发送的两个附加状态位。
图4.通过 DR 传输数据和状态位。
如上图4所示,在TAP调试模式下移入和移出DR的数据由2个数据位(位9至0)和1个状态位(位<>和<>)组成。当JTAG主站移入数据时,仅使用<>个数据位。两个状态位(仍必须移入)不由TAP控制器使用,可以设置为零或任何其他值。
在从 TAP 控制器移出的 10 位值中,两个状态位提供有关引导加载程序或调试引擎状态的信息。当引导加载程序运行时,通常只会遇到最后两种状态(调试繁忙或调试有效),因为其他两种状态值在引导加载程序模式下不会出现。(请参阅图 4 中的状态/条件。
如果状态位设置为调试繁忙(10b)值,则引导加载程序尚未读取由JTAG主站转移到DR中的先前值。这也意味着移出的 8 位数据值毫无意义,因为引导加载程序尚未完成命令。当收到此值时,JTAG主站必须使用先前传输的字节值重新加载DR,以便引导加载程序可以访问它。正确执行此操作的状态序列如下所示。
在将最后一个位转换为处于Shift-DR状态的DR并转换到Exit1-DR之后,JTAG主站应检查这两个状态位。
如果收到“调试繁忙”值,请切换到暂停 DR 状态,然后从那里转换到 Exit2-DR,然后再次返回到 Shift-DR。重新加载以前的 DR 值(不是这次移动的值),然后通过 Exit1-DR 和 Update-DR,忽略第二次传递时的状态位的值。
延迟一小段时间(取决于正在执行的命令),然后重试字节传输。
如果状态位设置为调试有效 (11b) 值,则引导加载程序已读取移入的最后一个字节并加载了回复字节。移出的 8 位值包含有效数据。
执行引导加载程序命令序列时,shiftDR 和 shiftDR_next 例程会自动执行此同步。应该为序列中的第一个字节调用 shiftDR 例程;为后续字节调用shiftDR_next。shiftDR_next例程的操作与 shiftDR 相同,只是它检查两个状态位并在必要时如上所述重新传输前一个 DR 字节。sendCommand 例程将所有这些联系在一起,并调用 shiftDR 和 shiftDR_next 来传输命令序列,根据需要延迟和重新发送字节,直到命令完成。
;============================================================================== ;= ;= sendCommand ;= ;= Transmits a loader command by shifting bytes through DR. ;= ;= Inputs : DP[0] - Points to area of RAM which stores input bytes ;= for command and which will be filled with output. ;= LC[1] - Number of bytes to transmit/receive ;= Outputs : C - Set on JTAG communication error ;= Destroys : AP, APC, A[0], A[1], A[2], A[15], PSW, LC[0] ;= sendCommand: move APC, #80h ; Acc => A[0], turn off auto inc/dec push LC[1] call waitForPrompt pop LC[1] jump C, sendCommand_fail move Acc, @DP[0] ; Read first byte to transmit call shiftDR push Acc move Acc, A[1] cmp #3 ; Should be valid status since we had a prompt pop Acc jump NE, sendCommand_fail move @DP[0], Acc ; Store first received byte move NUL, @DP[0]++ ; Increment data pointer djnz LC[1], sendCommand_loop jump sendCommand_pass sendCommand_loop: move A[2], #10 ; Number of retries allowed sendCommand_retry: move Acc, @DP[0] ; Get next byte to transmit call shiftDR_next push Acc move Acc, A[1] cmp #3 pop Acc jump NE, sendCommand_stall move @DP[0], Acc ; Store received byte move NUL, @DP[0]++ ; Increment data pointer djnz LC[1], sendCommand_loop jump sendCommand_pass sendCommand_stall: move LC[0], #8000 ; About a millisecond djnz LC[0], $ move Acc, A[2] sub #1 jump NZ, sendCommand_retry jump sendCommand_fail
引导加载程序提供的函数
MAXQ微控制器上的引导加载程序功能通常遵循共享模式,这意味着许多命令和状态码在器件之间是相同的。不同的设备可以实现引导加载程序命令集的不同子集,具体取决于其内部程序存储器的结构和其他要求。具体细节请参考您正在使用的MAXQ微控制器的用户指南补充。在这种情况下,请参考MAXQ2000用户指南补充文件中“在系统编程”部分中的引导加载程序命令。
与其他MAXQ引导加载程序一样,MAXQ2000引导加载程序提供的命令分为0到15的命令系列。每个命令都以命令字节开头,该字节是命令系列(前四位)和特定于命令的数字(低四位)的组合,如表 5 所示。作为一般规则,Family 0命令本质上是信息性的,由所有设备实现;其他命令系列是可选的。要确定特定MAXQ器件支持的命令系列,请参考器件的文档。族 0 命令 05h(获取支持的命令)返回一个位掩码,指示该引导加载程序支持哪些其他命令系列。
MAXQ2000支持的引导加载程序命令系列如下。
族 0 - 信息和状态。该系列中的命令可用于获取MAXQ器件的基本信息,包括ROM/引导加载程序的身份和版本、最新命令的结果(状态码)以及程序和数据存储器的大小。该系列还包括主擦除命令,该命令可清除设备上的所有程序和数据存储器。
族 1 - 荷载可变长度。该系列中的命令可用于加载程序(闪存)或数据(RAM)存储器。
族 2 - 转储可变长度。该系列中的命令可用于读取程序或数据存储器的内容。
系列 3 - CRC 可变长度。该系列中的命令可用于获取在指定的程序或数据存储器范围内计算的CRC-16值。
族 4 - 验证可变长度。该系列中的命令可用于验证指定范围的程序或数据存储器是否与JTAG主站提供的数据相匹配。
族 5 - 加载并验证可变长度。此系列中的命令将“加载”和“验证”命令的功能合并到一个命令中。
族 6 - 擦除可变长度。在MAXQ2000上,此命令可用于将指定区域的数据RAM清零。
族 7 - 擦除固定长度。在MAXQ2000上,该命令可用于擦除闪存程序存储器的单个页面,而不是使用主擦除命令一次擦除所有闪存。
一旦MAXQ微控制器上的TAP控制器被置于JTAG加载器模式(所有MAXQ微控制器的过程都相同),第一步就是确定加载器是否响应。执行此操作的最快方法是重复传输引导加载程序命令代码 00h(无操作)。每个引导加载程序命令完成后,引导加载程序会以提示符(“>”或 3Eh)字节进行响应。如果传输 00h 导致 3Eh 回复,则引导加载程序正在运行并准备好接受命令。
识别JTAG从属微控制器
引导加载程序启动并运行后,下一步是识别从MAXQ微控制器。族 0 命令 0Dh(获取 ID 信息)返回一个可变长度的 ASCII 字符串(零端接),用于标识设备和实用程序 ROM 版本。
我们的示例程序获取此信息,然后将其打印到串行端口作为演示的一部分。用于执行此操作的例程是getBanner。
;============================================================================== ;= ;= getBanner ;= waitForPrompt ;= shiftDR ;= clock0, clock1, shift ;= ;= Executes command 0Dh to retrieve the ROM (device ID) banner and prints ;= the banner text out over the serial port. ;= ;= Inputs : None ;= Outputs : C - Set on JTAG communication error ;= Destroys : AP, APC, Acc, PSW, LC[0] ;= getBanner: call waitForPrompt jump C, getBanner_fail move Acc, #CMD_GET_ROM_BANNER call shiftDR move Acc, #00h call shiftDR getBanner_loop: move Acc, #00h call shiftDR cmp #0FFh ; The banner is ASCII, so receiving this character ; most likely indicates that the JTAG lines are ; floating high and that no device is connected jump E, getBanner_fail jump Z, getBanner_done call txChar jump getBanner_loop getBanner_done: call txNewline jump getBanner_pass getBanner_fail: move C, #1 ret getBanner_pass: move C, #0 ret
擦除程序内存
MAXQ2000中包含的闪存程序存储器必须先擦除(设置为0FFFFh),然后才能编程。JTAG演示应用程序通过发送02h(主擦除)引导加载程序命令来擦除闪存,指示引导加载程序擦除所有程序和数据存储器。此命令还会清除密码锁定位,从而启用所有受支持的命令系列。
根据MAXQ器件的不同,此02h(主擦除)命令可能需要几秒钟才能完成。由于这是一个单字节命令,因此确定何时完成的最简单方法是发送无操作 (00h) 命令,然后连续 1 毫秒延迟,直到引导加载程序返回 3Eh,指示命令完成。此方法在下面的 masterErase 例程中显示。
;============================================================================== ;= ;= masterErase ;= ;= Executes command 02h (Master Erase) to clear all program and data memory. ;= ;= Inputs : None ;= Outputs : C - Set on JTAG communication error ;= Destroys : Acc, PSW, LC[0], LC[1] ;= masterErase: call waitForPrompt jump C, masterErase_fail move Acc, #CMD_MASTER_ERASE call shiftDR move Acc, #00h call shiftDR move LC[1], #5000 ; Number of retries before returning an error masterErase_loop: move Acc, #CMD_NOP call shiftDR cmp #3Eh jump E, masterErase_pass move LC[0], #8000 ; Delay for about a millisecond djnz LC[0], $ djnz LC[1], masterErase_loop masterErase_pass: move C, #0 ret masterErase_fail: move C, #1 ret
检索状态信息
在 MasterErase(或几乎任何其他引导加载程序命令)完成后,可以使用 04h(获取状态)命令来确定命令是否成功完成。“获取状态”命令返回两个字节的数据:一个字节包含状态标志(指示密码锁定设置/取消设置、字/字节模式处于活动状态以及其他信息),第二个字节是单字节状态代码。
如果最后一个命令成功完成,则状态代码始终为 00h(无错误)。非零状态代码表示错误。状态码的详尽列表见MAXQ2000用户指南补充程序;此处列出了一些常见的错误代码。
01h/02h - 不支持系列/命令无效。这些错误代码通常表示与JTAG主机的通信故障或对齐错误。如果JTAG主站发送的字节数多(或少)于引导加载程序对给定命令代码的预期,则引导加载程序会将其中一个数据字节解释为新命令的开始。
03h - 无密码匹配。此错误代码通常表示JTAG主站尝试使用受密码保护的命令(通常包括不在族0中的任何命令),而没有先清除密码锁。如果JTAG主站访问已将代码加载到字地址0010h – 001Fh中的部分,则会发生此错误。在这种情况下,部件必须被主擦除,或者必须使用密码匹配命令 (03h) 来解锁部件。
05h - 验证失败。加载/验证或验证命令上的验证步骤失败。此错误可能发生在通信故障之后,或者也因为尝试重新编程先前编程的闪存而没有先擦除它。
将代码加载到程序存储器中
为了演示JTAG引导加载程序的功能,演示应用需要通过JTAG连接将代码加载到从机MAXQ2000中。在此示例中,加载的代码将是启动 LCD 控制器并在 LCD 显示屏上显示 4 位数字的简单例程。
加载到从机MAXQ2000的代码基于以下简单的演示项目。
org 0 ljump main org 20h main: move LCRA, #03E0h ; xxx0001111100000 ; 00 - DUTY : Static ; 0111 - FRM : Frame freq ; 1 - LCCS : HFClk / 128 ; 1 - LRIG : Ground VADJ ; 00000 - LRA : RADJ = Min move LCFG, #0F3h ; 1111xx11 ; 1111 - PCF : All segments enabled ; 1 - OPM : Normal operation ; 1 - DPE : Display enabled move LCD0, #LCD_CHAR_0 move LCD1, #LCD_CHAR_0 move LCD2, #LCD_CHAR_0 move LCD3, #LCD_CHAR_2 move LCD4, #00h sjump $
但是,由于我们从演示应用程序而不是硬编码的十六进制文件加载代码字节(就像使用 MTK 或 MAX-IDE 加载代码时一样),演示应用程序将根据用户输入修改加载的应用程序。
在加载代码之前,JTAG通信演示应用程序首先指示用户输入一个4位十进制数,然后修改正在加载的应用程序,如下所示。
液晶屏上显示的数字是用户输入的 4 位数字。
密码区域的前四个字节(从字地址 010h 开始)被编程为用户输入的四个字符的 ASCII 值。
应用程序代码由演示使用对引导加载程序命令 10h(加载代码,可变长度)的三次调用来加载。此命令的参数包括:要加载的字节数(加载代码内存时必须是偶数以确保单词对齐);起始地址;当然,还有数据本身。MAXQ存储器是小端存储器,因此应用必须首先发送每个程序字的最低有效字节。
;;;; ;;;; First load - LJUMP 0020h at start of program memory ;;;; move DP[0], #0 move @DP[0], #CMD_LOAD_CODE_VARIABLE move @++DP[0], #4 ; Length - 4 bytes move @++DP[0], #00h ; AddrL (byte address 0000h) move @++DP[0], #00h ; AddrH move @++DP[0], #000h ; 00 0B 20 0C - ljump 0020h move @++DP[0], #00Bh move @++DP[0], #020h move @++DP[0], #00Ch move @++DP[0], #000h ; Padding move @++DP[0], #000h ; Padding move @++DP[0], #55h move LC[1], DP[0] move DP[0], #0 nop call sendCommand nop jump C, main_failJTAG call getStatus jump C, main_failJTAG move Acc, A[3] ; Check that loader status is 00h (no error) jump NZ, main_failStatus
其余两个代码内存块以类似的方式加载。
验证程序存储器中的代码
加载代码后,有几种方法可以验证它是否正确加载。
不要使用“加载代码可变长度”命令 (10h),而是使用“加载并验证代码可变长度”命令 (50h)。后一个命令将代码加载操作与验证步骤相结合。
或者,可以通过调用验证代码可变长度命令 (40h) 单独执行验证。
JTAG主机可以通过使用转储代码变量长度(20h)来验证加载的代码是否与发送的数据匹配,而不是让引导加载程序验证加载的代码。
从程序存储器转储代码
加载完所有代码后,JTAG演示的最后一步是在单个块中读出所有代码。此读取是通过将转储代码可变长度 (20h) 命令发送到引导加载程序来完成的。此命令的参数是要开始转储(读取)的地址和要转储的字节数。请注意,与所有JTAG引导加载程序命令一样,每个字节读回都必须发送一个字节。因此,此命令需要大量零键盘字节,每个字节将从程序存储器读取一个。
;;;; ;;;; Dump program code ;;;; move DP[0], #str_dumpCodeVariable call txString move DP[0], #0 move @DP[0], #CMD_DUMP_CODE_VARIABLE move @++DP[0], #1 ; Indicates single byte length (<256 bytes) move @++DP[0], #00h ; AddrL (byte address 0000h) move @++DP[0], #00h ; AddrH move @++DP[0], #128 ; Length - 128 bytes move LC[1], #128 main_loop1: move @++DP[0], #00h djnz LC[1], main_loop1 move @++DP[0], #000h ; Padding move @++DP[0], #000h ; Padding move @++DP[0], #55h move LC[1], DP[0] move DP[0], #0 nop call sendCommand nop jump C, main_failJTAG call getStatus jump C, main_failJTAG move Acc, A[3] ; Check that loader status is 00h (no error) jump NZ, main_failStatus
JTAG演示应用程序在读取这些代码字节后,通过串行端口以十六进制格式输出这些代码字节。
退出引导加载程序
单字节系列 0 命令 01h(出口加载程序)使引导加载程序完成操作并退出,如下所示。
引导加载程序启动内部复位,将 SPE 和 PSS 位清零并复位微控制器。
微控制器退出复位,并在8000h时开始在实用程序ROM中执行。
SPE 现在清除为零后,实用程序 ROM 代码会导致执行跳转到地址 0000h 处的用户应用程序代码的开头。
由于出口加载程序命令会自动清除SPE和PSS位,并且由于随后的复位将TAP控制器清除回旁路模式,因此JTAG主站无需执行这些操作中的任何一个。
;;;; ;;;; Exit loader mode and allow program code to execute ;;;; call waitForPrompt move Acc, #CMD_EXIT_LOADER call shiftDR move Acc, #00h call shiftDR move Acc, #00h call shiftDR move Acc, #00h call shiftDR
运行演示
运行JTAG引导加载程序演示需要以下硬件和软件组件。
硬件
两块MAXQ2000评估板(MAXQ2000-K00 REV B);一个评估板作为MAXQ2000主评估板,另一个作为MAXQ2000从基板。
MAXQ2000 LCD子板 (MAXQ2000-K01 REV B)
两根2×5JTAG接口电缆(包含在MAXQ2000评估板中)
串行转JTAG接口板(MAXQJTAG-001 REV B)
DB9直通串行电缆
两个 5V 稳压 (±5%) 直流壁式电源,中心柱正极,CUI Inc. DPR050030-P6 或同等电源
两个HC49US 8.00MHz晶体
2 × 5 个 0.100 英寸排针
软件
JTAG演示软件包
MAX-IDE开发环境MAXQ
用于MAXQ的微控制器工具包(MTK)
设置说明
如果尚未安装MAX-IDE,请根据MAXQ2000评估板附带的文档下载并安装。
如果尚未安装MTK,请根据MAXQ2000评估板附带的文档下载并安装。
将LCD子板连接到从机MAXQ3板上的接头J2000,如上图1所示。LCD子板应悬挂在MAXQ2000板的顶部。
在两块MAXQ8评估板上安装00.2000MHz晶体(Y1)。
将2×5 JTAG接头安装在主MAXQ2000套件的原型设计区,引脚连接到主MAXQ2000引脚,如表1所示。
配置串行转JTAG板和两个MAXQ2000评估板上的跳线和DIP开关,如表2所示。
将 DB9 串行电缆从 PC 上的 COM1 端口连接到串行到 JTAG 板上的 J1。
将第一个5V电源连接到串行转JTAG板上的J2。
将第二个5V电源连接到从MAXQ1评估板上的J2000。
将第一根JTAG电缆从串行转JTAG板上的P2连接到主MAXQ4板上的J2000。红线应通向两个JTAG接头上的引脚1。
将第二根JTAG电缆从主MAXQ2000板上的原型区JTAG接头连接到从MAXQ4板上的J2000。红线应通向两个JTAG接头上的引脚1。
编译和加载JTAG演示
下载JTAG演示软件包并将其解压缩到工作目录中。
启动 MAX-IDE。
打开两个 5V 电源的电源。
从菜单中选择“项目打开项目”,然后选择 maxqjtag.prj 项目文件将其打开。
从菜单中选择“调试”。应显示“生成成功”消息。
从菜单中选择“调试”。应出现一系列“正在加载”消息,然后是“完成”。
从菜单中选择“调试”。
关闭最大集成点。
关闭电源。
运行JTAG演示
关闭电源后,断开DB9串行电缆与串行转JTAG板的连接。
将DB9电缆连接到主MAXQ5套件板上的J2000。
启动 MTK。在启动对话框中选择“哑终端”。
从菜单中选择选项。在对话框中,将端口设置为 COM1,将速度设置为 9600 波特。
从菜单中选择以 1 波特率的目标。
打开电源。
如果应用程序已加载并且所有内容都已正确连接,则以下文本(图5)将通过串行端口输出。
图5.演示应用程序,串行端口提示符。
现在输入一个 4 位十进制数;之后无需按回车键。演示应用程序将完成其剩余操作(主擦除、加载代码和转储代码),并将输出结果以及从程序内存转储的字节的十六进制值,如图 6 所示。
图6.演示应用程序,串行端口输出。
结论
通过使用一组标准化命令,MAXQ微控制器提供的JTAG引导加载程序允许外部JTAG主机轻松识别和编程任何MAXQ微控制器。本应用笔记中包含的代码可作为构建功能齐全的JTAG引导加载程序主应用的起点,该应用能够识别、初始化、加载和验证任何支持标准化引导加载程序命令集的MAXQ微控制器的代码和数据存储器内容。
审核编辑:郭婷
-
微控制器
+关注
关注
48文章
7505浏览量
151149 -
JTAG
+关注
关注
6文章
398浏览量
71619 -
主机
+关注
关注
0文章
987浏览量
35091
发布评论请先 登录
相关推荐
评论