电量计 – HDQ调试过程 用GPIO INT pin(支持WKUP)实现HDQ接口,实现slave,配合host驱动通信,获取电量信息。并且和I2C接口兼容不冲突。
Keil sct和项目配置 (1) Keil工程配置
Keil的IROM,IRAM配置的主要作用是在编译期发现空间不够的问题,编译器提前报错。
IRAM只是代码段,不包含堆栈,所以IRAM大小要预留STACK和HEAP空间,不然编译期不报错,运行期STACK爆了很难查
(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中被更新。
Sleep time过程 Chip Sleep主要依赖于Timer1的timeout中断来计时(单位1HZ),Timer1还能同时配置wakeup中断使timeout时chip从sleep低功耗模式下恢复成全功率模式
Timer2基本没使用,Chip Idle状态下的sleep和定时active都是由timer1完成。
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。