📄 main.c
字号:
/* ----------读--卡--器--模--块--操--作--方--法 ----------
1: 模块寄存器分页面, 共8个页面, 地址为A2A1A0=000时, 写入0b10000xxx 选择不同的页面
此后的A2A1A0=xxx 对应该页不同的寄存器
2:上电后, 读Command寄存器, 直到末6位为0--->写Page寄存器为0x80--->读Command寄存器,若值为0,则成功,否则重新写Page=0x80.
3: 上电后,初始化读卡器芯片, 多数寄存器不进行初始化
4: 有电子钱包设置功能, 设置为电子钱包,设置为电子钱包的区域只能进行加或减操作,不能进行直接写操作(可以读)
5: 对卡进行操作的密钥是通过计算获取的, 对卡写的密钥选择B组密钥, 对卡读的密钥选择A组密钥
密钥计算方法为: 使用密钥A和B的计算方法相同, 但置换盒不同
----注意:置换盒数据从0~15.
扇区号2字节与卡号(4字节)组成密钥初始计算值 置换盒中包含8字节置换数据(高半字节及低半字节各为一个地址位)
上位机发来的置换盒按照接收顺序存放在置换盒从低地址到高地址的空间(内存中使用也是按照低到高地址顺序使用
按照下面的顺序置换:
扇区号为一个字(8K卡只有低字节有效) BIT15~BIT0为16个位, 卡号按照从卡读出的顺序,读出的第1字节放在
缓冲区低地址......读出的最后字节放在缓冲区最后位置, 从低地址开始取4个字节,前2字节组成一个字
低地址为高字节 高地址为低字节, 共产生3个字,BIT15~BIT0分别为他们的16个位.
置换盒使用顺序为:低地址数据的高半字节对应BIT15目的位置 低地址数据的低半字节对应BIT14目的位置
......高地址数据的高半字节对应BIT1目的位置 高地址数据的低半字节对应BIT0目的位置
置换后产生6字节按照:扇区号产生的新字高字节为密钥的第1字节,存放在密钥缓冲区最低地址,扇区号产生的新字低
字节为密钥的第2字节,存放在密钥缓冲区第2低地址,卡号第1和2字节产生的新字的高字节为密钥的第3字节
卡号第1和2字节产生的新字的低字节为密钥的第4字节, 卡号第3和4字节产生的新字的高字节为密钥的第5字节
卡号第3和4字节产生的新字的低字节为密钥的第6字节,存放在缓冲区最高地址处.
具体顺序为:
1) 扇区号高字节 BIT15 BIT14 ...... 扇区号低字节 BIT1 BIT0
置换盒低地址数据高半字节 置换盒低地址数据低半字节 ......置换盒高地址数据高半字节 置换盒高地址数据低半字节
按照置换盒对应位置的数据表示的新位置,把扇区号各对应位填到新的对应地址
扇区号新字 BITi ...... BITn ...... BITm ...... BITk
新字的高字节放user_key_buf[0] 新字的低字节放user_key_buf[1]
2) 卡号第1字节为高字节 BIT15 BIT14 ...... A卡号第2字节为低字节 BIT1 BIT0
置换盒低地址数据高半字节 置换盒低地址数据低半字节 ......置换盒高地址数据高半字节 置换盒高地址数据低半字节
按照置换盒对应位置的数据表示的新位置,把卡号2字节各对应位填到新的对应地址
新字 BITi ...... BITn ...... BITm ...... BITk
新字的高字节放user_key_buf[2] 新字的低字节放user_key_buf[3]
3) 卡号第3字节为高字节 BIT15 BIT14 ...... A卡号第4字节为低字节 BIT1 BIT0
置换盒低地址数据高半字节 置换盒低地址数据低半字节 ......置换盒高地址数据高半字节 置换盒高地址数据低半字节
按照置换盒对应位置的数据表示的新位置,把卡号2字节各对应位填到新的对应地址
新字 BITi ...... BITn ...... BITm ...... BITk
新字的高字节放user_key_buf[4] 新字的低字节放user_key_buf[5]
以上3次变换获得密钥,双方按照相同的格式进行变换, 发卡时直接写入卡中即可,使用时由上位机确定采用的密钥组
6: 获取卡ID后保存, 并向上位机发送, 上位机获取后读取电子钱包值, 如果是新卡没有在洗涤,则发出钱包减值, 如果不是,或是正在洗涤,
则只显示钱包值; 钱包减值后,上位机可以发出指令要求本读卡器重新询卡获取ID号,如果ID号一致,则可以开始洗涤.
7:对操作顺序:
复位读卡模块-->初始化读卡模块-->发出指令询卡-->有卡则发出防冲突(获取UID)-->读出UID-->发出选卡指令直到卡片应答正确
-->认证(对指定的块)-->读写卡 要对新块进行操作, 必须重新认证.
设置函数:询卡 防冲突 读UID 选卡 认证 读 写 加 减
其中认证前, 必须先把密钥存放在MF1702的密钥区, 密钥采用前面提到的算法进行处理, 由用户给出初始密钥,初始密钥可以直接写在
程序存储区(Key_Sta_Rom_Buf[]). 也可以使用指令传输到本读卡器.
8: 与上位机通讯指令为:
0x11 --> 握手指令, 本指令同时设置读卡器对应卡片的类型, 对不同类型的卡读写有不同,可以慢慢扩展,目前只支持FM11RF08,本指令暂不用
0x22 --> 设置卡某区为电子钱包模式
0x55 --> 电子钱包加值
0x33 --> 电子钱包减值
0x44 --> 写入卡指定区域指令
0x66 --> 读出卡指定区域指令,返回指令仍用0X66
0x77 --> 重新认证卡,读出卡ID号
0x88 --> 空操作
0x9x --> 向读卡器写入密钥置换盒
0xbb --> 已经处理完毕本张卡片, 延时后寻找下一张卡片.
0xcc --> 向上位机发送卡号指令
0xaa --> 应答指令
#A# UART指令结构: 帧开始 参数个数 指令 参数 校验和,
帧开始为0xff 0xff,满足帧开始的条件是接收到连续的两个0xff,且上次通讯结束后时间间隔大于20毫秒
参数个数为指令后所跟参数的个数 校验和为不包括帧开始和校验和本事所有其他字节的和
1) 0x11 握手指令, 一个完整的指令,用于确定一些基本参数,例如卡类型等,多数参数保留
0xff 0xff 0x04 0x11 para1 para2 para3 para4 sum
卡类型设置 para1=0为默认FM11RF005 FM11RF08类型的卡 以后可以扩展,
其余参数保留备用
应答: 0xff 0xff 0x01 0xaa ack sum ACK:为接收或执行情况 0:正确 其它错误
2) 0x22 设置卡某区为电子钱包模式, 参数包括扇区号(2字节) 块号(1字节) 采用的认证密钥组(mode) 备用参数(4BYTE)
0xff 0xff 0x08 0x22 SECTORH SECTORL BLK mode AUX1 AUX2 AUX3 AUX4 sum
应答: 0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
3) 0x55 电子钱包加值, 参数包括扇区号(2字节) 块号(1字节) 采用的认证密钥组(mode) 备用参数(2BYTE) 增加值H/L(2byte)
0xff 0xff 0x08 0x55 SECTORH SECTORL BLK AUX1 VAL_H VAL_L mode AUX2 sum
应答: 0xff 0xff 0x04 0xaa ack VAL_H VAL_M VAL_L sum VAL_H VAL_M VAL_L是电子钱包现值(高字节 中字节 低字节)
如果错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
4) 0x33 电子钱包减值, 参数包括扇区号(2字节) 块号(1字节) 采用的认证密钥组(mode) 备用参数(2BYTE) 减少值H/L(2byte)
0xff 0xff 0x08 0x33 SECTORH SECTORL BLK AUX1 VAL_H VAL_L mode AUX2 sum
应答: 0xff 0xff 0x04 0xaa ack VAL_H VAL_M VAL_L sum
VAL_H VAL_M VAL_L是电子钱包现值(高字节 中字节 低字节)
如果错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
5) 0x44 写入卡指定区域 参数包括扇区号(2字节) 块号(1字节),此后的数据为按照地址顺序写入的数据, 参数个数-3就是要写入卡的数据个数
对于FM11RF005 每块4字节 对于FM11RF08 每块16字节 采用的认证密钥组(mode)
0xff 0xff length 0x44 SECTORH SECTORL BLK DATA0~DATAi mode AUX sum
应答: 0xff 0xff 0x01 0xaa ack sum
ack=0:正确 0x0f:接收错误 其它为对应错误
6) 0x66 读出卡指定区域 参数包括扇区号(2字节) 块号(1字节)
对于FM11RF005 每块4字节 对于FM11RF08 每块16字节 采用的认证密钥组(mode)
0xff 0xff 0x05 0x66 SECTORH SECTORL BLK mode AUX sum
应答: 0xff 0xff length 0x66 ack DATA0~DATAi sum 读取结束返回这个
如果错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
7) 0x77 重新询卡,读出卡ID
0xff 0xff 0x02 0x77 aux1 aux2 sum
应答:0xff 0xff 0x08 0x77 CNU0......CNU7 sum 芯片序列号一般为4字节,保留前4字节,始终为00
如果接收错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
8) 0x88 空操作, 该指令没有任何含义, 发出后上位机也发出本指令,无需其它应答, 空闲时每隔一定时间发出, 双方可以互发,
0xff 0xff 0x02 0x88 aux1 aux2 sum 无应答
如果接收错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
9) 0x9x 向读卡器写入密钥置换盒 本指令需要不定时刷新, 建议每收到一定数量的空操作指令就重新刷新一次.
0xff 0xff 0x0a 0x9x key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 aux1 aux2 sum
应答 0xff 0xff 0x01 0x99 00 sum
如果错误, 则返回0xff 0xff 0x01 0xaa ack sum ack=0:正确 0x0f:接收错误 其它为对应错误
10)0xbb 已经处理完毕本张卡片, 延时后寻找下一张卡片.
上位机认为处理完毕本张卡片, 则发出本指令, 本读卡器停止对卡的操作, 且延时命令指定时间后重新询卡.
0xff 0xff 0x04 0xbb timer aux1 aux2 aux3 sum 其中timer表示延时时间长度 单位为10毫秒,接收后存放在reque_delay_t中
应答: 0xff 0xff 0x01 0xaa ack sum ack=0:接收正确 非0:接收错误
11)0xcc 向上位机发送卡号, 卡号为检索到的卡的号码
0xff 0xff 0x08 0xcc CNU0......CNU7 sum 芯片序列号一般为4字节,保留前4字节,始终为00
应答: 0xff 0xff 0x01 0xaa ack sum ack=0:接收正确 非0:接收错误
#B# 脉冲方式指令结构: 帧开始 参数个数 指令 参数 校验和
帧开始为:时钟信号出现连续6~10毫秒的低. 时钟信号空闲时必须为高电平,
两次发送间隔要大于10毫秒, 连续发送的两个字节要有大于50微秒的间隔
参数个数为指令后所跟参数的个数 校验和为不包括帧开始和校验和本事所有其他字节的和
脉冲方式为全双工同步方式 一组口为ROMI RCLK,为本板发出串行数据的口和时钟口;
另外一组为RIMO MCLK, 为上位机发出本板接收数据口和时钟口.
发出数据有效为时钟上沿有效,高电平保持数据,低电平可以变换数据.沿上升时间小于1微秒, 高电平时间大于100微秒
低电平时间大于50微秒,但建议不要大于100微秒
指令系统与UART通讯相同. 发送和接收不能同时进行,所有通讯过程是从本板发送卡号开始, 以后的发送在收到对方的应答后才能开始
时序关系为:
起始位
|<--------->|
------|_______________|----|____|----|____|----|____|----|____|----|____|----|____|
-------------------|________|---------|_________
|<------>|
第1个数据位
注:发送时先发字节的最高位,发送的写入卡的数据为先发低地址的数据后发高地址的数据.
#C#对于本板两个口根据口线选择使用发送和接收, 且接收的指令均立即执行, 但上位机只能选择一个口作为通讯口,且连接本通讯板的口必须有1K~10K电阻的上拉
SPI通讯没有0xff 做头标志 选择口线为: com_type_sel_kx
*/
/*
----------------------------本--板--编--程--思--路------------------------------------
1: 通讯口同时接收指令,接收后不立刻返回应答,而是处理后再返回,一般接收完成,存放在缓冲区,设置标志,处理程序处理完,然后返回应答
2: 对于SPI方式通讯, 在主流程中随时检测有没有低电平,如果有低电平则立刻开始进行连续10毫秒的检测,如果大于5毫秒,则认为
收到头,以后直接等待接收,同时进行计时,如果连续20毫秒没有数据信号,则表示通讯故障,清除接收过程,退出接收程序
下一次重新尝试. 收到完整的指令,则存放在缓冲区,设置标志等待处理
3: 收到指令有标志ins_spi_flg=1;处理程序直接从接收缓冲区取数据进行处理.
4: 发送数据则两个口方式同时发送. 不进行通讯故障的判断和处理.
5: 对接收位的处理,一般连续检测到两次相同电平认为电平已经确定,否则这个电平无效,同时置标志,本次通讯接收完成,返回接收错误应答
6: 接收时检测上沿的出现,必须连续两次检测到电平均已经变化认为时钟电平发生变化,此时开始读取数据信号.
7: 不采用中断方式*****可以允许读卡器芯片的接收中断RxIEn(标志RxIRQ),当有数据接收,则发出IRQ中断,激活MCU接收FIFO数据
8: 定时发出Transeive_Command(0x1E),查询是否有卡
9: 读卡器芯片的指令是用来实现与MCU的连接的,卡片的指令是对卡片操作的指令,要对卡片操作必须根据读卡器芯片指令向读卡器芯片FIFO
写入"需要向卡片发送的指令",读卡器芯片则发出这些指令给卡片,实现对卡片的操作
例如: 要查询是否有卡, 则需要向读卡器芯片发出Transeive_Command(0x1E)指令,向FIFO写入卡片的指令0x52/0x26(询卡指令REQALL/REQA)
此时如果有卡片,则卡片会返回卡片UID,读卡器芯片会收到UID,然后发出中断,告诉MCU有卡以及卡号.
要向卡片写入数据,则需要向读卡器芯片发出Transeive_Command(0x1E)指令,向FIFO写入卡片的指令WRITE以及要写入的数据,则读卡器芯片
自动把数据写入卡片,并返回写入结果,发出中断IRQ告诉MCU返回结果情况
10:查询到有卡,知道卡号,则向上位机发出有卡指令,告诉上位机卡号,等待上位机指令
11: SPI UART发送/接收缓冲区用1个就可以了,不单独设置(一个联合中).
####以后考虑上位机与本板的传输也采用加密机制
*/
#include <hidef.h> /* for EnableInterrupts macro */
#include <MC68HC908JL8.h> /* include peripheral declarations */
#include "rfcard.h"
void main(void)
{
asm{ //设置堆栈指针位置 从0x448开始向低地址扩展
LDHX #$0160
TXS
}
//CONFIG初始化
CONFIG2=0x80;
CONFIG1=0x11; //关闭WDT
ini_pp(); //端口及外围功能初始化
ini_ram();
ini_rf_module(); //初始化读卡器芯片
EnableInterrupts;
for(;;)
{
__RESET_WATCHDOG();
while (jsq10ms<10)
{
if (com_type_sel_kx) uart_tr_sub();
else spi_com_p();
}
jsq10ms=0;
card_availed_ct++;
if (card_availed_ct>=100) card_avai_kx=1;
if (reque_delay_t!=0) reque_delay_t--; //卡处理完毕,延时查询新卡
card_chk_p(); //查询是否有卡
card_proc(); //卡片处理
if (ins_spi_flg||ins_uart_flg) rf_ins_proc_p(); //处理接收指令
}
}
//----------------------------------------------------------------------//
//内部RAM初始化程序, //
//从00~1ff全部清0
//----------------------------------------------------------------------//
void ini_ram(void)
{
asm {//从0x0060开始联系清到0x120
LDHX #$60
CLRLOP: STA COPCTL
CLR ,X
AIX #1
CPHX #$120
BLO CLRLOP
}
}
//----------------------------------------------------------------------//
//内部外围功能初始化
//----------------------------------------------------------------------//
void ini_pp(void)
{
DisableInterrupts;
//对口操作
PTAPUE=0xFF;
PTA7PUE=0xFF;
PTA=0xff; //11111111
DDRA=0x9E; // 10011110;
PTB=0xFF;
DDRB=0xFF;
PDCR=0x00;
PTD=0xff; //11111111
DDRD=0x1F; //00011111
PTE=0xff;
DDRE=0x03; //00000011=
//IRQ1
INTSCR_IMASK=1;
INTSCR_ACK=1;
// INTSCR=0x02; //IRQ禁止中断
//TIMER1
//用于定时1毫秒
T1SC_TSTOP=1;
T1SC_TRST=1;
T1MODH=0x04; //1毫秒 1229个脉冲是1ms
T1MODL=0xcd;
T1SC&=0x7F; //清中断标志
T1SC_TOIE=1; //溢出中断允许
T1SC_TSTOP=0;
T2SC_TSTOP=1;
//SCI
//初始化1200BPS, 4.9152M振荡频率 0000 0000
//SCBR=0x00:19200 0x01:9600 0x02:4800 0x03:2400 0x04:1200
SCBR=0x02; //波特率寄存器
SCC1=0x40; //0100 0000
SCC2=0x24; //0010 0100
SCC3=0;
SCC2_SCRIE=1; //接收中断 允许
SCC2_RE=1; //接收允许
SCC1_ENSCI=1; //SCI ENABLE
// SCC1_LOOPS=1;
EnableInterrupts;
}
//----------------------------------------------------------------------//
//内部外围功能初始化--为抗干扰, 再处理
//----------------------------------------------------------------------//
void emi_p(void)
{
DDRA=0x9E; // 10011110;
DDRE=0x03; //00000011=
DDRB=0xFF;
DDRD=0x1F; //00011111
}
//----------------------------------------------------------------------//
//初始化读卡器芯片
//设置中断 口方式 收发等
//按照读卡器芯片要求,确认操作口方式
//写入和读取模块数据用void wr_1byte_rfm(uchar add,uchar data);uchar rd_1byte_rfm(uchar add);
//1:进行端口操作的确认 读command寄存器 写入page寄存器 再读
//----------------------------------------------------------------------//
void ini_rf_module(void)
{
uchar ls1;
//MF1702复位
rfm_rst_kx=1;
delay_ms(40);
rfm_rst_kx=0;
delay_ms(40);
ini_rf_module3:
ls1=rd_1byte_rfm(Command);
if (ls1!=0) goto ini_rf_module3;
//端口确定]
ini_rf_module1:
ls1=rd_1byte_rfm(Command);
if (ls1!=0) goto ini_rf_module1;
ini_rf_module2:
ls1=rd_1byte_rfm(Command);
if (ls1!=0) goto ini_rf_module2;
//初始化中断源
wr_1byte_rfm(InterruptEn,0x7f); //关闭所有中断
ls1=rd_1byte_rfm(InterruptEn);
wr_1byte_rfm(Int_Req,0x7f); //清所有中断标志
//设置MFOUT信号源
wr_1byte_rfm(MFOUTSelect,0x02); //MFOUTselect ????
//开启TX1 TX2
wr_1byte_rfm(TxControl,0x5b);
wr_1byte_rfm(TypeSH,0x00);
/*
// wr_1byte_rfm(CoderControl,0x20); //
// wr_1byte_rfm(TypeBFraming,0x05); //
// wr_1byte_rfm(DecoderControl,0x19);
wr_1byte_rfm(ChannelRedundancy,0x00); ////选择无校验 *****CRC校验以后研究
wr_1byte_rfm(RxWait,0x04); //RXWAIT
//对于校验错误,有PAGE 1 的ErrorFlag寄存器表示, 通过查询ParityErr获得校验结果
wr_1byte_rfm(TxControl,0x5b);
wr_1byte_rfm(CWConductance,0x3f); //阻抗设置--等于复位默认值--保证正确而再初始化
// wr_1byte_rfm(ModConductance,0xaf);
*/
//初始化 control寄存器 清空FIFO
wr_1byte_rfm(Control,0x01);
}
//----------------------------------------------------------------
//如果询卡发现有卡存在,则向上位机发出卡UID, 等待回复,100毫秒没有回复, 则重新发送, 直到成功通讯
//crdpro_fen1 0:准备 1:发出数据 2:等待回复 3:完成返回
//----------------------------------------------------------------
void card_proc(void)
{
uchar ls1,i;
if (!rfcard_in_flg)
{
crdpro_fen1=0;
return;
}
if (crdpro_fen1==0)
{
if (comming_uart_flg) return;
crdpro_fen1++;
crdpro_fen1_ct=0;
}
else if (crdpro_fen1==1)
{
tr_buf[0]=0xff;
tr_buf[1]=0xff;
tr_buf[2]=8; //个数
tr_buf[3]=0xcc; //指令
tr_buf[4]=0;
tr_buf[5]=0;
tr_buf[6]=0;
tr_buf[7]=0;
tr_buf[8]=rfcard_sn_buf[0];
tr_buf[9]=rfcard_sn_buf[1];
tr_buf[10]=rfcard_sn_buf[2];
tr_buf[11]=rfcard_sn_buf[3];
ls1=tr_buf[2];
for (i=3;i<12;i++)
{
ls1+=tr_buf[i];
}
tr_buf[12]=ls1;
tr_uart_count=0;
nd_uart_tr_nu=13; //应发送数据个数
nd_spi_tr_nu=13; //应发送数据个数
comming_spi_flg=1;
spi_tr_strt_ct=0;
spi_tr_strt_flg=0;
comming_uart_flg=1;
tran_uartenable();
rfcard_runing_flg=1;
crdpro_fen1++;
recv_reply_flg=0;
}
else if (crdpro_fen1==2)
{
if ((!recv_reply_flg)||recv_reply_errflg)
{
if (crdpro_fen1_ct>=250)
{
crdpro_fen1=0;
}
}
else crdpro_fen1++;
}
else
{//此后不再进行卡号处理,直到没有卡有效,然后重新开始.
}
}
//----------------------------------------------------------------
//卡片检测程序, 查找有效卡片并读出卡片UID
//每隔10毫秒调用本函数一次, 如果已经有卡在操作,则本函数不再进行询卡
//发出指令询卡. 获取卡片返回参数, 应该是0x04 0x00
//输入mode 表示询卡方式 RF_Requ_ALL=0x52指令 RF_Requ_STD=0x26指令
//写入和读取"射频卡读写模块"数据用void wr_1byte_rfm(uchar add,uchar data);uchar rd_1byte_rfm(uchar add);
//获取卡片类型放在rfcard_type, 00:卡片是8K卡 01:卡片是其它卡 其它:无效
//发现卡片存在, 且知道序列号,则设置rfcard_in_flg=1, 表示有卡需要操作
//如果查询出错,则返回,等待下一次程序询卡.
//----------------------------------------------------------------
void card_chk_p(void)
{
uchar ls1,ls2=0;
if (rfcard_runing_flg) return; //有卡在操作中,则不再查询卡
if (reque_delay_t!=0) return; //未到延时时间不询卡
rfcard_in_flg=0;
card_chk_p2:
ls1=request_card(RF_Requ_ALL); //询卡
if (ls1!=0) return;
card_chk_p1:
ls2++; //超时处理
if (ls2>10)
{
rfcard_in_flg=0;
return;
}
ls1=anti_coll(); //防冲突
ls1&=0xf0;
if (ls1==0x10) return;
else if ((ls1==0x20)||(ls1==0x30)) goto card_chk_p1;
ls1=select_card();
ls1&=0xf0;
if(ls1==0x10)
{
rfcard_in_flg=0;
rfcard_runing_flg=0;
card_avai_kx=1;
return;
}
else if ((ls1==0x20)||(ls1==0x30)||(ls1==0x40)||(ls1==0x50)||(ls1==0xc0)) goto card_chk_p2;
rfcard_in_flg=1;
rfcard_runing_flg=1;
card_avai_kx=0;
card_availed_ct=0;
reque_delay_t=200;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -