ThinkNotes

Simple is not easy | 化繁为简,知易行难

0%

电量计 -- HDQ调试过程

电量计 – HDQ调试过程

用GPIO INT pin(支持WKUP)实现HDQ接口,实现slave,配合host驱动通信,获取电量信息。并且和I2C接口兼容不冲突。

Keil sct和项目配置

(1) Keil工程配置

Keil的IROM,IRAM配置的主要作用是在编译期发现空间不够的问题,编译器提前报错。

IRAM只是代码段,不包含堆栈,所以IRAM大小要预留STACK和HEAP空间,不然编译期不报错,运行期STACK爆了很难查

image-20250716114617057

(2) sct链接配置

真正配置IROM和IRAM空间分布的是.sct文件,如下是newton FW sct

sct的含义参考Keil文档,这里不详细描述。需要会区分IROM,IRAM,知道(+RO)表示代码段,(+RW +ZI)表示数据段,.ANY和*.o是通配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

;;Flash mapping, total 64KB EEPROM
;0x0~0x2000 8KB bootloader code
;0x2000~0xD3FF 45KB Firmware code
;0xD400~0xF3FF 8KB Table and Parameter data
;0xF400~0xFFFF 3KB log page and trim data

;;RAM mapping, total 4KB RAM
;0x20000000 ~ 0x20000400 1KB bootloader data
;0x20000400 ~ 0x20000F80 3KB-128 firmware data
;0x20000F80 ~ 0x20001000 128 bytes remain for stack and heap

LR_IROM1 0x00000000 0x00002000 {
ER_IROM1 0x00000000 0x00002000 { ; bootloader 8KB, load address = execution address
;.ANY (+RO)
*.o (RESET, +First)
;*(InRoot$$Sections)
;startup_armcm0.o
;system_ARMCM0.o
;newton_o2bootloader_main.o
;systime_o2bl.o
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00000400 { ; RW data, 1KB
;.ANY (+RW +ZI)
newton_o2bootloader_main.o (+RW +ZI)
}
RW_STACK +0 UNINIT ALIGN 16 ; RW data - Stack <<< THIS SECTION IS WHAT I ADDED
{
*.o (Stack)
*.o (Heap)
}
}

LR_IROM2 0x00002000 0x0000B400 { ; FW ROM 45KB(0xB400), +Bootloader 8KB(0x2000) = 53KB, 后面还有8KB
ER_IROM2 0x00002000 0x0000B400 {
;.ANY (+RO)
*(InRoot$$Sections)
main.o
...
}

;RW_IRAM2 0x20000460 0x00000BA0 { ; original setting, 0x460+0xBA0=0x1000 4KB
RW_IRAM2 0x20000400 0x00000B80 { ; RW data, 3KB - 128 bytes(remain for stack)
;.ANY (+RW +ZI)
main.o (+RW +ZI)
...
.ANY (+RW +ZI) ; Catch-all for any remaining BSS sections
}
}

(3) RAM紧缺的优化方法

修改大数组,添加const使编译器从原本是RW数据区的数组放置到ROM区,减少RAM占用。

注意只读数组可以这么改,修改后数组在EEPROM/Flash,无法在RAM中被更新。

image-20250716113642727

Sleep time过程

Chip Sleep主要依赖于Timer1的timeout中断来计时(单位1HZ),Timer1还能同时配置wakeup中断使timeout时chip从sleep低功耗模式下恢复成全功率模式

Timer2基本没使用,Chip Idle状态下的sleep和定时active都是由timer1完成。

image-20250718114147290

HDQ调试–UART方式

UART打印只需要使用slave的TX,即GPIO TX;UART打印一次约一百毫秒以上,会影响毫秒级别HDQ timing,secure CRT看到的uart时间戳并不是真实的HDQ timing时间戳。因此UART打印只能用来调试秒级的HDQ的基本逻辑功能。

HDQ调试–GPIO方式

基本GPIO配置:(INT和TX都可用于调试信号,配置模式不一样)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "chip_uart.h"
void Set_INTGPIO(uint8_t val)
{
Chip_IO_SetINTCFG(EXINTCFG_MODE_OUTPUT_PUSH_PULL | EXINTCFG_PU_ENABLE);
UART->CTL &= ~(UART_CTRL_RX_ENABLE);
if(val)
Chip_IO_SetINT_DOUT(EXINTDAT_OUTPUT_HIGH);
else
Chip_IO_SetINT_DOUT(EXINTDAT_OUTPUT_LOW);
}

void Set_TXGPIO(uint8_t val)
{
Chip_IO_SetGPIOCFG(GPIOCFG_MODE_DIGI_OUTPUT); //注意模式是DIGI_OUTPUT,不是OUTPUT_PUSH_PULL
UART->CTL &= ~(UART_CTRL_TX_ENABLE);
if(val)
Chip_IO_SetGPIO_DOUT(GPIODATA_OUTPUT_HIGH);
else
Chip_IO_SetGPIO_DOUT(GPIODATA_OUTPUT_LOW);
}

调试信号函数:输出n+1次脉冲(1 & 0):

1
2
3
4
5
6
static void hdq_txgpio_dbg_signal(int n){
do{
Set_TXGPIO(1);
Set_TXGPIO(0);
}while(n--);
}

调用可在中断内,例如HDQ WKUP handler,TIM1 hander:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//HDQ WKUP isr内调用,用dslogic查看break信号是否超时:   

hdq_txgpio_dbg_signal(1);

// 等待break结束(检测上升沿)
while (!hdq_gpio_read()) {
if (hdq_wait_timeout(hdq_ctrl.break_start_ms, 3*HDQ_BREAK_MAX_MS)) {
// break超时,退出
dbg_print("HDQ: Break timeout, elapsed=%dms\r\n", hdq_timer_elapsed_ms(hdq_ctrl.break_start_ms, hdq_timer_get_ms_count()));
hdq_ctrl.state = HDQ_STATE_IDLE;
hdq_enable_sleep();

hdq_txgpio_dbg_signal(2);
return;
}
}

hdq_txgpio_dbg_signal(1);
1
2
3
4
5
6
7
8
void TIM1_IRQHandler(void)
{
//每次timeout中断翻转一次TX,用于检测中断时间是否准确
int gpio = Chip_IO_GetGPIO_DOUT();
Set_TXGPIO(gpio? 0: 1);

TIMER16_1->TIMINTR |= 1; // TIMER_INT_FLAG;
}

HDQ帧间sleep后不能再wkup

drive sleep前需要初始化配置HDQ的GPIO模式,避免被其他配置干扰

HDQ fast sleep

一个SBS通信期间需要多个HDQ帧,如果每个帧都等待systick idle 1s sleep,一次SBS通信太慢,所以需要SBS通信active时,HDQ快速进入sleep

具体方法:新增HDQ register定义:SBS_STATUS_REG_ADDR用于Host通知slave,什么时候SBS开始,什么时候结束,SBS期间slave主动drive sleep不用等idle。

Host侧读SBS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 发SBS_STATUS_REG_ADDR = 1表示启动了SBS通信会有多个HDQ帧,
sd77561_hdq_write_byte(chip, SBS_STATUS_REG_ADDR, 1);

// 写CMD
sd77561_hdq_write_byte(chip, CMD_REG_ADDR, sbs_cmd_code);
sd77561_hdq_wait_slave_sleep(HDQ_SBS_RUNNING_WAIT_MS);

// 读数据
for (indx = 0; indx < size; indx++)
{
if (sd77561_hdq_read_byte(chip, DATA0_REG_ADDR + indx, &sbs_data[indx]) < 0)
return -1;
sd77561_hdq_wait_slave_sleep(HDQ_SBS_RUNNING_WAIT_MS);
}

// 读CRC(CMD+Data的)
sd77561_hdq_read_byte(chip, CRC_REG_ADDR, &crc_slave) ;

// 发SBS_STATUS_REG_ADDR = 0表示完成了SBS通信,固件会恢复1s idle sleep时间
sd77561_hdq_wait_slave_sleep(HDQ_SBS_RUNNING_WAIT_MS);
sd77561_hdq_write_byte(chip, SBS_STATUS_REG_ADDR, 0);

Slave侧在SBS的一个HDQ完成后,但SBS没结束的状态下,以ms级别轮询,主动drive sleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (systime_passed_ms())
{
#if HDQ_SW_ENABLE
// dbg_print("systime_get_ms(): %d\r\n", systime_get_ms());
// dbg_print("hdq_timer_get_count(): %d\r\n", hdq_timer_get_count());

//如果只用HDQ帧idle就进sleep,会导致秒级算法轮询无法更新因为ms级别polling一定比秒级先执行,
//所以额外使用SBS状态判断,必须是SBS通信过程中的HDQ帧间idle才快速进入sleep,SBS通信标志由host写HDQ reg status设置和清除
if(hdq_ctrl.state == HDQ_STATE_IDLE) {
// 检查SBS状态,如果SBS通信进行中则快速进入sleep以便下一帧HDQ
if (hdq_ctrl.registers.sbs_status_reg & HDQ_SBS_STATUS_RUNNING) {
stm_drive_sleep(10, STM_SLPMD_SLEEP); // SBS通信中,快速sleep,下个HDQ break会马上WKUP
} else{ //SBS通信结束,恢复timer和isr使能, 让VADC/CADC能更新
hdq_timer_restore(); //关闭hdq 1ms timer,避免频繁中断影响cadc等其他中断的更新,因为timer优先级很高
hdq_isr_restore(); //恢复cadc唤醒等中断,之前hdq启动时关闭了防止充电时频繁唤醒导致hdq通信不能进sleep
}
}
#endif
}

注:

1.如果不用HDQ_SBS_STATUS_RUNNING限制毫秒级别sleep,会影响秒级的libfg更新算法,所有电量数据将不变。

2.WKUP中断内drive sleep不生效,所以只能在ms级别轮询里做。

HDQ充电时无法进sleep

是因为CADC中断有触发条件:当电流大于CADC触发阈值,就会自动wakeup更新CADC。所以hdq drive sleep被cadc中断提前唤醒了。

解决:在HDQ帧开始禁用CADC wakeup, HDQ帧(或SBS帧)完成后restore CADC wakeup。

image-20250729102943925