1 oem_main.c
1.1 Hook_Timer1msEvent(1ms心跳)
1.1.1 按电源状态选择分支
对象 | S0、1 运行 | S3 睡眠(手动睡眠) | S5 关机 | G3 断主电 |
|---|---|---|---|---|
CPU/PCH | 全速 | 断电,上下文在 DRAM | 断电 | 断电 |
DRAM | 工作 | 自刷新带电 | 断电 | 断电 |
主板 MAIN 电 | 开 | 关 | 关 | 关 |
ALWAYS/SUS 电 | 开 | 开 | 开 | 关(拔电/无电池) |
EC(IT5571) | 跑全量任务 | 跑,仅值守唤醒 | 跑,守 SLP_S5/拔插电 | 掉 ALWAYS 才停 |
EC 代码分支 |
|
|
|
|
if ((SysPowState == _SYSTEM_S0) || (SysPowState == _SYSTEM_S1)) { Oem_SysPowState_S0_S1(); }跑 S0/S1 的全量业务:扫键盘、控风扇、充放电算法、PECI 温度、LED 指示等
else if (SysPowState == _SYSTEM_S3) { Oem_SysPowState_S3(); }只保最低唤醒:LID 开关、电源键、键盘唤醒、Type-C 插拔唤醒。不读 CPU 温度、不控风扇
else if ((SysPowState == _SYSTEM_S4) || (SysPowState == _SYSTEM_S5)) { #if SUPPORT_SW_MIRROR if (IS_MASK_CLEAR(EC_SW_MIRROR, SW_MIRROR_EN)) { Oem_SysPowState_S4_S5(); } #else Oem_SysPowState_S4_S5(); #endif }笔记本电脑已关机,但电池还有电:EC做最基础的看门狗(因为EC管机器的上电功能,即就是开机,故需要一直低功耗运行)
else if (IS_MASK_CLEAR(SysPowState, F_PST_BUSY)) { Oem_SysPowState_G3(); }拔适配器+没电池,或 EC 主动拉死。(G3为兜底情况,正常流程S0/S3/S4/S5 都被前面拦住)
1.1.2 System Power Control(上电/下电序列)
if (IS_MASK_SET(SysPowState, F_PST_BUSY)) { Oem_SysPowSequence(); // System Power Control } else { if (EventId & 0x01) { /* Divide Timer2msEvent */ SMBusA_ScanCenter(); // Cypress Type-C PD } else { /* Divide Timer2msEvent */ SMBusB_ScanCenter(); // PollSmartCharger,PollSmartBattery } }若是检测到电源为1,只跑系统开关的函数,因为开机需要时间,电源,其余模块不一定被拉起了
若是为0,则持续保持低功耗运行
SMBusB → 智能充电器 + 智能电池(老式 SMBus 电池协议)
SMBusA → Cypress Type-C PD 控制器(CCG 系列)
EventId | 奇偶 | 跑什么 | 间隔 |
|---|---|---|---|
0,2,4,6,8 | 偶 |
| 每 2ms |
1,3,5,7,9 | 奇 |
| 每 2ms |
2 UART
2.1 Init_UART2(串口)
void Init_UART2(void) { AUTOCG = 0x00; GCR1 &= 0xF3; //SIN1/SOUT1 are enabled. GCR1 |= 0x04; GCR6 = 0x0F; GCR8 &= 0xF3; //0x16F7 GCR8 |= 0x04; RSTDMMC = 0x1F; RSTC4 = BIT2; //Reset UART2 (RUART2) UART2_LCR = 0x80; //Bit7: DLAB=1 UART2_RBR = 0x01; //UART2_IER = UART2_DLL (DLAB=1) UART2_IER = 0x00; //UART2_IER = UART2_DLM (DLAB=1) // 0x00 0x01 : 115200 Baud Rate UART2_LCR = 0x03; UART2_MCR = 0x00; UART2_IIR = 0xC7; //(W)UART2_IIR = UART2_FCR (Use FIFO) UART2_IER = 0x01; GPCRH2 = ALT; //SOUT1 //GPCRH1 = ALT; //SIN1 #if 0 UART2_RBR = 'U'; UART2_RBR = 'A'; UART2_RBR = 'R'; UART2_RBR = 'T'; UART2_RBR = '2'; UART2_RBR = ' '; UART2_RBR = 'O'; UART2_RBR = 'K'; UART2_RBR = ' '; #endif printf("\n************************************"); printf("\nEC PRINTF UART2 OK !!!"); printf("\n************************************"); } #endif2.1.1 关时钟门控(防配置中途乱跑)
AUTOCG = 0x00;AUTOCG = Auto Clock Gating 控制寄存器。
写 0x00 表示:关闭自动门控,强制给 UART2 模块供时钟。
⚠️ FAE 高频坑:如果这里没开时钟,后面所有 UART2_xxx = xx 都等于写空气,示波器上绝对没波形,但代码不报错
2.1.2 引脚复用 & 驱动能力配置(GCR1 / GCR6 / GCR8)
GCR1 &= 0xF3; // SIN1/SOUT1 enable GCR1 |= 0x04; GCR6 = 0x0F; GCR8 &= 0xF3; // 0x16F7 GCR8 |= 0x04;EC 把 GPIO 复用、上拉、驱动强度、施密特触发等统一塞进几个全局GCRx(Global Control Register):
清0:&=0xF3 清掉 bit3~2(0xF3 转成 8 位二进制是:1111 0011);
|= 0x04 只把 bit2 置 1 (0000 0100)→ 使能 SIN1(RX)、SOUT1(TX);
注释里的 0x16F7 是客户原厂参考设计的“魔数”,别抄数值,要抄意图:把 TX 脚设成 ALT + 合适驱动。
这两个寄存器的 Bit2、Bit3确实都涉及 UART2,但它们分工不同,且 GCR1 还同时管着 UART1。
1. GCR1:同时控制 UART1 与 UART2
- Bit 3-2 (UART2 Control / U2CTRL):正是你之前代码
GCR1 &= 0xF3; GCR1 |= 0x04;操作的位置。写成0b10(即 0x04)对应手册的10b,使能了SIN1/SOUT1/DSR1/RTS1/CTS1/DTR1/DCD1。这里出现了一个手册命名细节:GCR1 的 Bit3-2 虽然叫 UART2 控制,但描述的引脚却是SIN1/SOUT1(通常对应 UART1 的引脚名,实际以引脚表为准)。 - Bit 1-0 (UART1 Control / U1CTRL):控制真正的 UART1(引脚为
SIN0/SOUT0)。你之前的代码&= 0xF3刚好也保留了 Bit1-0 的原值(未被清除)。
2. GCR8:也涉及 UART2,且有额外含义
- Bit 3-2 (UART2 Control / U2CTRL):同样控制 UART2。代码
GCR8 &= 0xF3; GCR8 |= 0x04;设为0b10,对应使能SIN1/SOUT1/RTS1/CTS1。 - Bit 1-0 (UART1 Control / U1CTRL):截图中 GCR8 底部也出现了 UART1 控制位(引脚为
SIN0/SOUT0等)。 - 关键差异(Bit4):GCR8 的 Bit4 是
PWRSW WDT 2 Enable,这与电源/看门狗有关,说明 GCR8 不仅是引脚复用。
3. 为什么两个寄存器都要配置?
既然都控制 UART2,为何代码两处都写?
- 功能互补:GCR1 的 UART2 模式(10b)包含完整的流控/调制解调引脚(DSR/DTR/DCD),而 GCR8 的 UART2 模式(10b)包含部分流控(RTS/CTS)。两者配合才能完整开启 UART2 所需功能。
- 历史/兼容性:ITE 芯片常有多组复用,GCR1 和 GCR8 可能对应不同物理引脚组,但都映射到 UART2 功能。
- 代码惯性:SDK 初始化模板常把相关 GCR 都刷一遍,确保状态一致。
GCR6 = 0x0F; 的本质是:独立开启 UART1 与 UART2 的收发引脚,且完全契合手册默认值。
结合前序讨论(GCR1/GCR8 负责整体引脚复用模式),GCR6 在这里负责独立使能收发器:
UART2(对应你关注的 SIN1/SOUT1):Bit3=1, Bit2=1 → 收发全开。
UART1(对应 SIN0/SOUT0):Bit1=1, Bit0=1 → 收发全开。
值得注意的是,手册中 Bit0~3 的默认值(Default)全都是 1b。因此这行代码虽然写死了 0x0F(高4位强制清0),但在核心串口使能上,它相当于显式确认了默认状态,确保 UART2(SIN1/SOUT1)和 UART1(SIN0/SOUT0)的收发均处于使能状态。
总结:
- GCR1 / GCR8:配置 Bit2-3,选定 UART2 的复用模式(如流控与引脚组映射)。
- GCR6:最终拍板,单独打开这些引脚的 TX(SOUT)和 RX(SIN)通道。
2.1.3 硬件复位 UART2 模块
RSTDMMC = 0x1F; RSTC4 = BIT2; // Reset UART2 (RUART2)RSTDMMC:释放/控制各模块复位状态机(先写解锁序列 0x1F)。
RSTC4 = BIT2:拉低 bit2 → UART2 模块硬复位,复位完成后硬件自动释放。
💡 为什么先复位再配? 寄存器上电初值不确定,硬复位一次,保证 LCR/FCR/IER 都回到 16550 默认态(DLAB=0,无 FIFO),配置才可控。
**2.1.4波特率设置(16550 经典三步:开 DLAB → 写 DLL/DLM → 关 DLAB)
UART2_LCR = 0x80; // Bit7: DLAB=1(允许访问波特率除数锁存器) UART2_RBR = 0x01; // DLL = 低8位 (除数低字节) UART2_IER = 0x00; // DLM = 高8位 (除数高字节) // 0x00 0x01 : 115200 Baud Rate UART2_LCR = 0x03; // DLAB=0, 8N1这是UART 最核心的一段,记住公式:
DLAB=1时,RBR(接收缓冲)和IER(中断使能)这两个地址,被硬件重定向成DLL(除数低字节) 和DLM(除数高字节)。DLL=0x01, DLM=0x00→ 16 位除数 =0x0001 = 1。- 反推:
UART_CLK / (1×16) = 115200→ 说明这颗 EC 的 UART2 时钟源被配成了1.8432 MHz(或 3.6864MHz/2,具体看APBCLK/UARTCLK分频)。
FAE 现场:客户换晶振,你只改 DLL/DLM 两个值,或者调时钟分频宏,不用动逻辑。 - 最后
UART2_LCR = 0x03→ 二进制0000 0011:bit1~0 = 11→8 位数据位bit2 = 0→1 位停止位bit3 = 0→ 无奇偶校验- 即8N1,PC 串口助手必须一致,否则全乱码。
2.1.5 关 Modem 控制 + 开 FIFO
UART2_MCR = 0x00; // 无流控 RTS/CTS,无环回 UART2_IIR = 0xC7; // 写 FCR:使能 FIFO,清 TX/RX FIFO,14字节触发级MCR=0:不用硬件流控(EC 调试口 99% 都是三线 TX/RX/GND)。
IIR 寄存器在 16550 里是只读的(读=中断标识),但往它地址写,实际是写 FCR(FIFO Control Register):
0xC7 = 1100 0111
bit7~6 = 11 → 触发深度 14 bytes(收到 14 字节才中断,减 IRQ 负担)
bit3 = 1 → DMA 模式(部分型号)
bit2 = 1 → 清空 TX FIFO
bit1 = 1 → 清空 RX FIFO
bit0 = 1 → FIFO 使能
2.1.6 开接收中断
UART2_IER = 0x01; // 使能 Receive Data Available 中断- 之前
IER=0x00是配波特率时临时借用;现在波特率定好了,再正式开“收到数据就打断 EC”。 - 注意:这里只开了 RX 中断。如果后面要做命令行交互(比如进 BIOS setup 敲
ESC),靠的就是它;只打 log 的话其实可以不开,但开了也不碍事。
2.1.7 引脚切复用 + 首发 log
GPCRH2 = ALT; // SOUT1 → 实际硬件 TX 脚 // GPCRH1 = ALT; // SIN1 → RX 脚先不用 printf("\nEC PRINTF UART2 OK !!!");GPCRx = ALT 就是把 GPIO 切成串口功能。
这里只开 TX 脚(GPCRH2),RX 脚注释掉 → 说明这个 EC 当前只往外吐日志,不接命令。
#if 0 那段往 RBR 手动塞 'U''A''R''T'... 是上古裸机调试法:直接写发送保持寄存器,不用 printf。现在有 printf 重定向,这招退休了,但客户老代码里常见,你知道它是“手动吐字符”就行
2.1.8 总结
AUTOCG=0 供时钟
↓
GCR1/GCR6/GCR8 配引脚电气 & 复用使能
↓
RSTC4 硬复位 UART2 → 模块归零
↓
LCR.7=1 → 写 DLL=0x01 / DLM=0x00 → 定 115200
↓
LCR=0x03 → 8N1,DLAB 关闭
↓
MCR=0 / FCR=0xC7 → 无流控 + 开 FIFO
↓
IER=0x01 → 开接收中断
↓
GPCRH2=ALT → TX 脚浮出波形
↓
printf → PC 串口助手看到 "UART2 OK"
2.2 相关知识
2.2.1 需确定引脚、波特率、电平
1.pin对应
根据芯片spec可知RXD/TXD分别在5571芯片的108(GPB0)、109(GPB1)pin,将串口与芯片对应pin连接
2.波特率
波特率使用115200, 8N1, 无流控
3.电平
ITE5571 是 3.3V TTL,USB-TTL 模块跳线切到 3.3V,千万别用 5V 挡直接接信号线
2.2.2 硬件连线(断电接)
USB-TTL ↔ ITE5571 开发板
- TTLTX → 板子RX2
- TTLRX → 板子TX2
- TTLGND → 板子GND(必须共地)
- TTL 的VCC 不接(板子自己供电)
接完先别上电,确认 TX/RX 是交叉的、电平是 3.3V。
2.2.3 代码层:在初始化之后加“会动的东西”
你已有 UART2 初始化函数,接着做三小块:
- 在
main()或 EC 上电后的某个任务里调用一次 UART2_Init()(确认时钟、波特率除数、FIFO、中断/DMA 模式和你学的函数一致)。 - 写一个最朴素的发送函数(或调用 SDK 的
uart_putc/Uart2Send):void uart2_puts(const char *s){ while(*s){ UART2_SEND_BYTE(*s++); while(!UART2_TX_EMPTY()); } } - 在初始化后加:
uart2_puts("ITE5571 UART2 hello\r\n");
再搞个回环/echo:收到什么发什么,或者按键盘 'a' 返回 'A',用来验证 RX 也通。
如果 SDK 里有printf重定向到 UART2 的钩子(EC 代码里常见putchar/SERIAL_OUTPUT=UART2),直接重定向printf更省事。
2.2.4 PC 端收数据
- 装好 CH340/CP2102 驱动,设备管理器看 COM 号(Linux 下
/dev/ttyUSB0)。 - 打开串口工具(MobaXterm / PuTTY / SSCOM / minicom 都行),设115200-8-N-1-无流控。
- 板子上电或按 Reset,应该能看到
ITE5571 UART2 hello。 - 没出字?先不怀疑代码,按下面排错。
2.2.5 常见“没输出”排查顺序
- 示波器/逻辑分析仪抓 TX2 脚:上电瞬间有无 0/1 跳变 → 有跳变说明 MCU 在发,是 PC 端或波特率问题;完全死高/死低说明 MUX 没配或 UART 没使能。
- 互换 TX/RX(新手最常错)。
- GND 没共地(最隐蔽)。
- 波特率算错:EC 内部时钟源(内部 IRC 还是外部晶振)和初始化函数里填的 divisor 不匹配。
- UART2 在 EC 里被 BIOS/Host 域占用,需要确认你的固件镜像里 UART2 分配给了 EC 域。
2.2.6 跑通后你可以继续做的实验(由浅入深)
- 串口命令行:解析 PC 发来的
led on/led off控制开发板 LED - 用 UART2 把 ADC 采到的电压值定时打印出来
- 改成中断接收,做带缓冲的命令行
- 把 UART2 和 UART0(EC 调试口)对比,理解 EC 多路 UART 的时钟域
- 最后再回头读 datasheet 里 UART2 的Line Control / FIFO Control / INT Enable 寄存器,和你刚才看的初始化函数逐行对上号
最小下一步:今天就把“初始化 + 上电打一句 hello”跑通,看到字再从 PC 发一个字节让灯翻转。这一关过了,UART 在 IT5571 上就算真正入门了。