exchg.lua 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. --[[
  2. @module exchg
  3. @summary exchg扩展库
  4. @version 1.0
  5. @date 2025.11.25
  6. @author 王世豪
  7. @usage
  8. -- 应用场景
  9. 本扩展库适用于Air8000/8000G/8000XB/8000GB等集成了内置电池充电方案的模组型号;
  10. Air8000/8000G/8000XB/8000GB内置的充电IC为YHM2712,exchg扩展库基于本充电IC进行设计;
  11. -- 用法实例
  12. 本扩展库对外提供了以下5个接口:
  13. 1)开启充电 exchg.start()
  14. 2)关闭充电 exchg.stop()
  15. 3)设置电池的充电截止电压,电池容量,充电电流 exchg.setup(v_battery, cap_battery, i_charge)
  16. 4)获取充电系统状态信息 exchg.status()
  17. 5)注册事件回调函数 exchg.on(func)
  18. 其中,开启充电 exchg.start() 和 关闭充电 exchg.stop() 默认自动执行,用户可以不用操作;
  19. 当碰到某些需要手动关闭或开启充电功能的场景时,大家可以自行控制,当前仅为预留;
  20. 以下为exchg扩展库四个函数的详细说明及代码实现:
  21. 1、开启充电
  22. 必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。
  23. @api exchg.start()
  24. @return boolean: true=成功, false=失败
  25. 2、关闭充电
  26. 必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。
  27. @api exchg.stop()
  28. @return boolean: true=成功, false=失败
  29. 3、设置电池的充电截止电压,电池容量,充电电流
  30. 必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。
  31. @api exchg.setup(v_battery, cap_battery, i_charge)
  32. @param number v_battery: 电池充电截止电压(单位:mV), 取值范围:4200或4350可选, 必须传入。
  33. @param number cap_battery: 电池容量(单位:mAh), 取值范围:>= 100,必须传入。
  34. @param string i_charge: 充电电流, 取值范围:exchg.CCMIN(最小电流) 或 exchg.CCDEFAULT(默认电流) 或 exchg.CCMAX(),三个可选参数,不传入时默认值为exchg.CCDEFAULT。
  35. @return boolean: true=成功, false=失败
  36. @usage
  37. exchg.setup(4200, 400, exchg.CCMIN) -- 设置电池充电截止电压为4.2V, 电池容量为400mAh, 充电电流为最小电流
  38. 4、获取充电系统状态信息
  39. 必须在task中运行,最大阻塞时间(包括超时重试时间)大概为20s。
  40. 该函数用于获取当前充电系统的完整状态,包括电池电压、充电阶段、充电状态、电池在位状态、充电器在位状态以及IC过热状态等信息。
  41. 其中充电器是否在位,中断触发,触发回调事件为CHARGER_STATE_EVENT,附带的参数 true表示充电器在位,false表示充电器不在位。
  42. @api exchg.status()
  43. @return table 状态信息表
  44. {
  45. result = boolean, -- true: 成功, false: 失败
  46. vbat_voltage = number, -- 电池电压值(单位:mV),特殊值含义:
  47. -- -1: 当前阶段不需要测量
  48. -- -2: 电压测量失败
  49. -- -3: 仅充电器就绪(无电池)
  50. charge_stage = number, -- 当前充电阶段描述,可能值:
  51. -- 0 : 放电模式
  52. -- 1 : 预充电模式
  53. -- 2 : 涓流充电
  54. -- 3 : 恒流快速充电
  55. -- 4 : 预留状态
  56. -- 5 : 恒压快速充电
  57. -- 6 : 预留状态
  58. -- 7 : 充电完成
  59. -- 8 : 未知状态
  60. charge_complete = boolean, -- true: 充电完成, false: 充电未完成
  61. battery_present = boolean, -- true: 电池在位, false: 电池不在位
  62. charger_present = boolean, -- true: 充电器在位, false: 充电器不在位
  63. ic_overheat = boolean -- true: 充电IC过热, false: 充电IC未过热
  64. }
  65. 5、注册事件回调函数
  66. @api exchg.on(func)
  67. @function: 回调方法,回调时传入参数有exchg.OVERHEAT, exchg.CHARGER_IN, exchg.CHARGER_OUT
  68. @return nil 无返回值
  69. @usage
  70. local function exchg_callback(event)
  71. if event == exchg.OVERHEAT then
  72. log.info("警告:设备温度过高!")
  73. elseif event == exchg.CHARGER_IN then
  74. log.info("充电器已插入")
  75. elseif event == exchg.CHARGER_OUT then
  76. log.info("充电器已拔出")
  77. end
  78. end
  79. -- 注册回调
  80. exchg.on(exchg_callback)
  81. 示例:
  82. local function exchg_task_func()
  83. exchg.setup(4200, 400)
  84. while true do
  85. local status = exchg.status()
  86. if status.result then
  87. log.info("电池电压:", status.vbat_voltage,
  88. "充电阶段:", status.charge_stage,
  89. "充电是否完成:", status.charge_complete,
  90. "电池在位:", status.battery_present,
  91. "充电器在位:", status.charger_present,
  92. "IC过热:", status.ic_overheat)
  93. end
  94. sys.wait(20000)
  95. end
  96. end
  97. -- 事件回调函数
  98. local function exchg_callback(event)
  99. if event == exchg.OVERHEAT then
  100. log.info("警告:设备温度过高!")
  101. elseif event == exchg.CHARGER_IN then
  102. log.info("充电器已插入")
  103. elseif event == exchg.CHARGER_OUT then
  104. log.info("充电器已拔出")
  105. end
  106. end
  107. -- 注册回调
  108. exchg.on(exchg_callback)
  109. sys.taskInit(exchg_task_func)
  110. ]]
  111. local exchg = {}
  112. -- yhm2712 cmd引脚, 传入nil表示根据模组型号自动选择
  113. local gpio_pin = nil
  114. --yhm2712芯片地址
  115. local sensor_addr = 0x04
  116. --电压控制寄存器地址
  117. local V_ctrl_register = 0x00 -- read/write
  118. --电流控制寄存器地址
  119. local I_ctrl_register = 0x01 -- read/write
  120. --模式寄存器地址
  121. local mode_register = 0x02 -- read/write
  122. --配置寄存器,默认为0x00
  123. local config_register = 0x03 -- read/write
  124. --状态寄存器
  125. local status1_register = 0x05 -- read only
  126. local status2_register = 0x06 -- read only
  127. --id寄存器
  128. local id_register = 0x08 -- read only
  129. --充电电压参数,默认门限电压为4.35V
  130. local set_4V2 = 0x04 --4.2V
  131. local set_4V35 = 0x64 --4.35V
  132. local set_4V = 0xE4 --4V
  133. --充电电流参数,默认充电电流为175mA,即0.5倍*250=175mA
  134. local set_0I2 = 0x20 --0.2倍,0.2*250=50mA
  135. local set_0I5 = 0x00 --0.5倍,0.5*250=125mA
  136. local set_0I7 = 0x40 --0.7倍,0.7*250=175mA
  137. local set_0I9 = 0x60 --0.9倍,0.9*250=225mA
  138. local set_I = 0x80 -- 1倍,1.0*250=250mA
  139. local set_1I5 = 0xA0 --1.5倍,1.5*250=375mA
  140. local set_2I = 0xC0 -- 2倍,2.0*250=500mA
  141. local set_3I = 0xE0 -- 3倍,3*250=750mA
  142. -- 实际电流值(mA)到十六进制参数的映射
  143. local current_to_register = {
  144. [50] = set_0I2,
  145. [125] = set_0I5,
  146. [175] = set_0I7,
  147. [225] = set_0I9,
  148. [250] = set_I,
  149. [375] = set_1I5,
  150. [500] = set_2I,
  151. [750] = set_3I
  152. }
  153. -- 检测USB状态,测VBUS脚(即gpio.WAKEUP1)
  154. local vbus_pin = gpio.WAKEUP1
  155. -- 是否正在充电
  156. local is_charge = false
  157. -- 是否仅充电器在位
  158. local charger_only = false
  159. -- 电池电压采样值数组
  160. local nochg_t = {}
  161. -- nochg_t 最大长度限制
  162. local AVR_MAX = 10
  163. local callback = nil
  164. -- 充电门限电压设置,默认4.35V
  165. local voltage_setting = set_4V35
  166. -- 充电电流常量
  167. exchg.CCMIN = "MIN" -- 恒流充电MIN电流模式
  168. exchg.CCMAX = "MAX" -- 恒流充电MAX电流模式
  169. exchg.CCDEFAULT = "DEFAULT" -- 恒流充电默认电流模式,电流大小处于Min和Max之间
  170. -- 定义事件常量
  171. exchg.OVERHEAT = 1 -- 温度过热事件
  172. exchg.CHARGER_IN = 2 -- 充电器插入事件
  173. exchg.CHARGER_OUT = 3 -- 充电器拔出事件
  174. -- 使用表格存储不同容量和模式下的电流值
  175. local current_table = {
  176. [100] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 50, [exchg.CCMAX] = 50},
  177. [200] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 125, [exchg.CCMAX] = 125},
  178. [300] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 175, [exchg.CCMAX] = 175},
  179. [400] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 225, [exchg.CCMAX] = 225},
  180. [500] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 250, [exchg.CCMAX] = 250},
  181. [600] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 250, [exchg.CCMAX] = 375},
  182. [700] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 375, [exchg.CCMAX] = 500},
  183. [800] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 375, [exchg.CCMAX] = 500},
  184. [900] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 375, [exchg.CCMAX] = 500},
  185. [1000] = {[exchg.CCMIN] = 50, [exchg.CCDEFAULT] = 500, [exchg.CCMAX] = 750}
  186. }
  187. --[[
  188. 注册exchg事件回调
  189. @api exchg.on(func)
  190. @function 回调方法,回调时传入参数有exchg.OVERHEAT, exchg.CHARGER_IN, exchg.CHARGER_OUT
  191. @return nil 无返回值
  192. @usage
  193. local function exchg_callback(event)
  194. if event == exchg.OVERHEAT then
  195. log.info("警告:设备温度过高!")
  196. elseif event == exchg.CHARGER_IN then
  197. log.info("充电器已插入")
  198. elseif event == exchg.CHARGER_OUT then
  199. log.info("充电器已拔出")
  200. end
  201. end
  202. -- 注册回调
  203. exchg.on(exchg_callback)
  204. --]]
  205. function exchg.on(cb)
  206. callback = cb
  207. end
  208. -- 内部事件触发函数
  209. local function notify_event(event)
  210. if callback then
  211. callback(event)
  212. end
  213. end
  214. -- 查找最接近的电池容量标准值(最小100mAh),使用四舍五入规则
  215. local function get_closest_capacity(capacity)
  216. -- 四舍五入到最近的100的倍数
  217. local rounded = math.floor(capacity / 100 + 0.5) * 100
  218. -- 确保结果不小于100mAh
  219. rounded = math.max(100, rounded)
  220. return rounded
  221. end
  222. --[[
  223. 设置电池充电截止电压,电池容量,充电电流(必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。)
  224. @api exchg.setup(v_battery, cap_battery, i_charge)
  225. @number v_battery: 电池充电截止电压, 取值范围:4200或4350可选, 单位(mV), 必须传入。
  226. @number cap_battery: 电池容量, 取值范围:>= 100, 单位(mAh),必须传入。
  227. @string i_charge: 充电电流, 取值范围:exchg.CCMIN(最小电流) 或 exchg.CCDEFAULT(默认电流) 或 exchg.CCMAX(最大电流),三个可选参数,不传入时默认值为exchg.CCDEFAULT。
  228. @return boolean: true=成功, false=失败
  229. @usage
  230. exchg.setup(4200, 400, exchg.CCMIN) -- 设置电池充电截止电压为4.2V, 电池容量为400mAh, 充电电流为最小电流
  231. ]]
  232. function exchg.setup(v_battery, cap_battery, i_charge)
  233. -- 验证电池电压
  234. if v_battery ~= 4200 and v_battery ~= 4350 then
  235. log.error("exchg", "无效的电池电压,必须是 4200 (4.20V) 或 4350 (4.35V)")
  236. return false
  237. end
  238. -- 验证电池容量范围
  239. if type(cap_battery) ~= "number" or cap_battery < 100 then
  240. log.error("exchg", "电池容量过低, 小于100mAh")
  241. return false
  242. end
  243. -- 获取最接近的标准容量值
  244. local closest_capacity = get_closest_capacity(cap_battery)
  245. -- 处理充电电流参数,默认为CCDEFAULT
  246. local charge_current = i_charge or exchg.CCDEFAULT
  247. -- 验证充电电流参数
  248. if i_charge ~= nil and charge_current ~= exchg.CCMIN and charge_current ~= exchg.CCDEFAULT and charge_current ~= exchg.CCMAX then
  249. log.error("exchg", "无效的充电电流参数,必须是 exchg.CCMIN、exchg.CCDEFAULT 或 exchg.CCMAX,已使用默认值")
  250. charge_current = exchg.CCDEFAULT -- 重置为默认值
  251. end
  252. -- 获取电流值,如果容量超过1000mAh,则使用1000mAh的配置
  253. local actual_capacity = math.min(closest_capacity, 1000)
  254. local actual_current = current_table[actual_capacity][charge_current]
  255. -- 根据实际电流值获取对应的十六进制参数
  256. local current_register_value = current_to_register[actual_current]
  257. if not current_register_value then
  258. log.error("exchg", "未找到对应电流值的寄存器参数: " .. actual_current)
  259. return false
  260. end
  261. -- 读取芯片ID,验证通信是否正常
  262. local result, data = pm.chgcmd(gpio_pin, sensor_addr, id_register)
  263. if not result then
  264. log.error("exchg", "无法读取芯片ID, 通信失败")
  265. return false
  266. end
  267. -- 设置电池充电截止电压
  268. voltage_setting = v_battery == 4200 and set_4V2 or set_4V35
  269. result,data = pm.chgcmd(gpio_pin, sensor_addr, V_ctrl_register, voltage_setting)
  270. if not result then
  271. log.error("exchg", "设置电池充电截止电压失败")
  272. return false
  273. end
  274. -- 设置充电电流
  275. result,data = pm.chgcmd(gpio_pin, sensor_addr, I_ctrl_register, current_register_value)
  276. if not result then
  277. log.error("exchg", "设置电池充电电流失败")
  278. return false
  279. end
  280. sys.wait(200) -- 写入命令之后等待200ms再去读取寄存器数据,必须要等待,否则会有读取寄存器失败的可能。
  281. -- 请求寄存器数据
  282. pm.chginfo(gpio_pin, sensor_addr)
  283. local reg_result, reg_data = sys.waitUntil("YHM27XX_REG", 500)
  284. if reg_result and reg_data then
  285. local V_value = reg_data:byte(1)
  286. local I_value = reg_data:byte(2)
  287. -- 验证设置是否生效
  288. if V_value == voltage_setting and I_value == current_register_value then
  289. return true
  290. else
  291. log.warn("exchg.setup未生效, 请检查是否支持yhm27xx")
  292. return false
  293. end
  294. else
  295. log.error("exchg.setup" .. "0x04寄存器数据获取失败")
  296. return false
  297. end
  298. end
  299. --[[
  300. 开始充电(必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。)
  301. @api exchg.start()
  302. @return boolean: true=成功, false=失败
  303. @usage
  304. exchg.start() -- 开始充电
  305. ]]
  306. function exchg.start()
  307. -- 读取芯片ID,验证通信是否正常
  308. local result, data = pm.chgcmd(gpio_pin, sensor_addr, id_register)
  309. if not result then
  310. log.error("exchg", "无法读取芯片ID, 通信失败")
  311. return false
  312. end
  313. sys.wait(200) -- 写入命令之后等待200ms再去读取寄存器数据,必须要等待,否则会有读取寄存器失败的可能。
  314. -- 开启充电前先查询ic温度,如果过热,则不执行开启充电功能
  315. pm.chginfo(gpio_pin, sensor_addr)
  316. result, data = sys.waitUntil("YHM27XX_REG", 500)
  317. if not result or not data or #data < 6 then
  318. log.error("exchg.start1" .. "0x04寄存器数据获取失败")
  319. return false
  320. end
  321. -- 提取0x05寄存器的第3位(从0开始计数)
  322. -- 过热标志位为1,表示温度>120℃
  323. -- 过热标志位为0,表示温度<120℃
  324. local overheat = (data:byte(6) & 0x08) ~= 0
  325. if overheat then
  326. log.error("exchg.start" .. "ic温度过高,不执行充电功能")
  327. return false
  328. end
  329. -- 开始充电
  330. result = pm.chgcmd(gpio_pin, sensor_addr, mode_register, 0xA8)
  331. if not result then
  332. log.error("exchg.start", "开始充电失败")
  333. return false
  334. end
  335. sys.wait(200) -- 写入命令之后等待200ms再去读取寄存器数据,必须要等待,否则会有读取寄存器失败的可能。
  336. -- 请求寄存器数据
  337. pm.chginfo(gpio_pin, sensor_addr)
  338. local reg_result, reg_data = sys.waitUntil("YHM27XX_REG", 500)
  339. if reg_result and reg_data then
  340. if reg_data:byte(3) == 160 then
  341. log.info("exchg.start 生效")
  342. return true
  343. else
  344. log.warn("exchg.start 未生效, 请检查是否支持yhm27xx")
  345. return false
  346. end
  347. else
  348. log.error("exchg.start2" .. "0x04寄存器数据获取失败")
  349. return false
  350. end
  351. end
  352. --[[
  353. 停止充电(必须在task中运行,最大阻塞时间大概为700ms, 阻塞主要由sys.waitUntil("YHM27XX_REG", 500)和sys.wait(200)产生。)
  354. @api exchg.stop()
  355. @return boolean: true=成功, false=失败
  356. @usage
  357. exchg.stop() -- 停止充电
  358. ]]
  359. function exchg.stop()
  360. -- 读取芯片ID,验证通信是否正常
  361. local result = pm.chgcmd(gpio_pin, sensor_addr, id_register)
  362. if not result then
  363. log.error("exchg", "无法读取芯片ID, 通信失败")
  364. return false
  365. end
  366. result = pm.chgcmd(gpio_pin, sensor_addr, mode_register, 0xF8)
  367. if not result then
  368. log.error("exchg.stop", "停止充电失败")
  369. return false
  370. end
  371. sys.wait(200) -- 写入命令之后等待200ms再去读取寄存器数据,必须要等待,否则会有读取寄存器失败的可能。
  372. -- 请求寄存器数据
  373. pm.chginfo(gpio_pin, sensor_addr)
  374. local reg_result, reg_data = sys.waitUntil("YHM27XX_REG", 500)
  375. if reg_result and reg_data then
  376. if reg_data:byte(3) == 240 then
  377. log.info("exchg.stop 生效")
  378. return true
  379. else
  380. log.warn("exchg.stop 未生效, 请检查是否支持yhm27xx")
  381. return false
  382. end
  383. else
  384. log.error("exchg.stop" .. "0x04寄存器数据获取失败")
  385. return false
  386. end
  387. end
  388. -- 检测电池是否在位
  389. local function check_battery_exists()
  390. local switch_count = 0
  391. local last_status = nil
  392. local loop_count = 0
  393. local total_loops = 50 -- 50次循环, 每次循环至少100ms
  394. for loop_count = 1, total_loops do
  395. -- log.debug("当前循环", loop_count, "/", total_loops)
  396. -- 发送读取请求
  397. pm.chginfo(gpio_pin, sensor_addr)
  398. local result, data = sys.waitUntil("YHM27XX_REG", 200)
  399. -- 存储解析后的寄存器数据
  400. local Data_reg = {}
  401. if result and data then
  402. for i=1,9 do
  403. Data_reg[i] = data:byte(i)
  404. end
  405. -- 提取Data_reg[7](对应0x06寄存器)的7:4 位
  406. -- 1. 使用0xF0(二进制1111 0000)进行按位与操作,保留高4位
  407. -- 2. 右移4位将结果转换为十进制
  408. local current_status = (Data_reg[7] & 0xF0) >> 4
  409. -- log.debug("状态:", current_status)
  410. -- 只关注状态12(1100)和13(1101)
  411. if current_status == 12 or current_status == 13 then
  412. if last_status ~= nil and last_status ~= current_status then
  413. switch_count = switch_count + 1
  414. -- log.debug("状态切换", last_status, "->", current_status, "次数:", switch_count)
  415. -- 达到切换阈值提前退出
  416. if switch_count >= 2 then
  417. -- log.info("电池判定", "不在位(频繁切换)")
  418. return false
  419. end
  420. end
  421. last_status = current_status
  422. else
  423. last_status = nil
  424. end
  425. else
  426. log.error("check_battery_exists", "0x04寄存器数据获取失败")
  427. last_status = nil
  428. end
  429. -- 确保每次循环间隔至少100ms
  430. if loop_count < total_loops then
  431. sys.wait(100)
  432. end
  433. end
  434. -- log.debug("检测结束", "切换次数:", switch_count)
  435. if switch_count >= 2 then
  436. return false
  437. else
  438. return true
  439. end
  440. end
  441. --[[
  442. 获取当前的充电阶段状态
  443. -- 充电状态说明:
  444. -- 0 (000): 放电模式
  445. -- 1 (001): 预充电模式
  446. -- 2 (010): 涓流充电
  447. -- 3 (011): 恒流快速充电
  448. -- 4 (100): 预留状态
  449. -- 5 (101): 恒压快速充电
  450. -- 6 (110): 预留状态
  451. -- 7 (111): 充电完成
  452. ]]
  453. local function get_charge_status()
  454. pm.chginfo(gpio_pin, sensor_addr)
  455. local result, data = sys.waitUntil("YHM27XX_REG", 500)
  456. -- 存储解析后的寄存器数据
  457. local Data_reg = {}
  458. if result and data then
  459. for i=1,9 do
  460. Data_reg[i] = data:byte(i)
  461. end
  462. -- 提取充电状态信息
  463. -- 充电状态位于0x05寄存器(对应Data_reg[6])的高3位(第7-5位)
  464. -- 1. 使用0xE0(二进制1110 0000)进行按位与操作,保留高3位
  465. -- 2. 右移5位,将高3位移到最低位
  466. local charge_status = (Data_reg[6] & 0xE0) >> 5
  467. return charge_status
  468. else
  469. log.error("get_charge_status", "0x04寄存器数据获取失败")
  470. return nil
  471. end
  472. end
  473. -- 查询充电ic是否过热
  474. local overheat_check_timer = nil
  475. local function check_over_heat()
  476. pm.chginfo(gpio_pin, sensor_addr)
  477. local result, data = sys.waitUntil("YHM27XX_REG", 500)
  478. if not result or not data or #data < 6 then
  479. log.error("check_over_heat", "0x04寄存器数据获取失败")
  480. return false
  481. end
  482. -- 提取0x05寄存器的第3位(从0开始计数)
  483. -- 过热标志位为1,表示温度>120℃
  484. -- 过热标志位为0,表示温度<120℃
  485. local overheat = (data:byte(6) & 0x08) ~= 0
  486. if overheat then
  487. -- 充电IC过热, 大于120℃, 停止充电!
  488. exchg.stop()
  489. notify_event(exchg.OVERHEAT)
  490. -- 如果已有定时器在运行,先停止
  491. if overheat_check_timer then
  492. sys.timerStop(overheat_check_timer)
  493. overheat_check_timer = nil
  494. end
  495. -- 启动定时器,10分钟后再次检查
  496. overheat_check_timer = sys.timerStart(check_over_heat, 10 * 60 * 1000)
  497. else
  498. -- 温度正常
  499. if overheat_check_timer then
  500. -- 停止定时器(因为温度已恢复,不再需要检查)
  501. sys.timerStop(overheat_check_timer)
  502. overheat_check_timer = nil
  503. -- 重新启动充电
  504. exchg.start()
  505. end
  506. end
  507. return true, overheat
  508. end
  509. -- 启用或禁用SYS_TRACK电压跟随功能 0x01寄存器
  510. function set_sys_track(enable)
  511. if type(enable) ~= "boolean" then
  512. log.error("set_sys_track: 无效的enable参数,必须是布尔值")
  513. return false
  514. end
  515. local reg_addr = 0x01 -- SYS_TRACK所在寄存器地址
  516. local reg_value = 0x00 -- 初始值
  517. local max_retry = 3 -- 最大重试次数
  518. local retry_count = 0 -- 重试计数
  519. -- 读取当前寄存器值
  520. while retry_count <= max_retry do
  521. pm.chginfo(gpio_pin, sensor_addr)
  522. local result, data = sys.waitUntil("YHM27XX_REG", 500)
  523. local Data_reg={}
  524. if result then
  525. -- 将data按字节解析到Data_reg数组
  526. for i=1, #data do
  527. Data_reg[i] = data:byte(i)
  528. end
  529. reg_value = Data_reg[2]
  530. break
  531. else
  532. retry_count = retry_count + 1
  533. log.warn("set_sys_track: 读取寄存器失败,正在重试 (" .. retry_count .. "/" .. max_retry .. ")")
  534. if retry_count > max_retry then
  535. log.error("set_sys_track: 超过最大重试次数,读取寄存器失败")
  536. return false
  537. end
  538. sys.wait(100)
  539. end
  540. end
  541. -- 保存原始值,用于比较
  542. local original_value = reg_value
  543. -- 设置SYS_TRACK位 (bit 1)
  544. if enable then
  545. reg_value = reg_value | 0x02 -- 设置bit 1为1 (0x02 = 00000010)
  546. else
  547. reg_value = reg_value & 0xFD -- 设置bit 1为0 (0xFD = 11111101)
  548. end
  549. -- 如果值没有变化,不需要写入
  550. if reg_value == original_value then
  551. -- log.info("set_sys_track: SYS_TRACK" .. (enable and "启用" or "禁用") .. "状态已保持,无需写入")
  552. return true
  553. end
  554. -- log.info("set_sys_track: 修改后寄存器值", string.format("0x%02X", reg_value))
  555. -- 写入新值
  556. retry_count = 0
  557. while retry_count <= max_retry do
  558. local result, data = pm.chgcmd(gpio_pin, sensor_addr, reg_addr, reg_value)
  559. if result then
  560. -- log.info("set_sys_track: SYS_TRACK" .. (enable and "启用" or "禁用") .. "成功")
  561. return true
  562. else
  563. retry_count = retry_count + 1
  564. log.warn("set_sys_track: 写入寄存器失败,正在重试 (" .. retry_count .. "/" .. max_retry .. ")")
  565. if retry_count > max_retry then
  566. log.error("set_sys_track: 超过最大重试次数,写入寄存器失败")
  567. return false
  568. end
  569. sys.wait(100) -- 重试前等待100ms
  570. end
  571. end
  572. end
  573. -- 中断检测充电器是否在位(通过检测VBUS引脚的电平来判断充电器是否在位),并对外发布CHARGER_STATE_EVENT事件
  574. local function check_charger()
  575. if gpio.get(vbus_pin) == 0 then
  576. if is_charge then
  577. is_charge = false
  578. notify_event(exchg.CHARGER_OUT)
  579. end
  580. else
  581. if not is_charge then
  582. is_charge = true
  583. notify_event(exchg.CHARGER_IN)
  584. end
  585. end
  586. end
  587. -- 初始化GPIO中断
  588. gpio.debounce(vbus_pin, 500, 1) -- 消抖
  589. gpio.setup(vbus_pin, check_charger, gpio.PULLUP, gpio.BOTH) -- 上拉电阻+双沿触发
  590. check_charger() -- 初始检测
  591. -- 向滑动窗口数组添加新的电压采样值,并计算窗口内的平均值, 用于平滑电压采样数据
  592. local function append_vadc(v)
  593. -- 如果窗口已满(达到最大长度 AVR_MAX)
  594. if #nochg_t >= AVR_MAX then
  595. -- 添加新值到窗口末尾
  596. table.insert(nochg_t, v)
  597. -- 移除窗口最前面的旧值
  598. table.remove(nochg_t, 1)
  599. else
  600. -- 窗口未满时,用当前值填充整个窗口
  601. -- 这确保初始化阶段也存在有效的平均值
  602. while #nochg_t < AVR_MAX do
  603. table.insert(nochg_t, v)
  604. end
  605. end
  606. -- 计算窗口内所有值的总和
  607. local totv = 0
  608. local min_val = nochg_t[1]
  609. local max_val = nochg_t[1]
  610. for i = 1, #nochg_t do
  611. totv = totv + nochg_t[i]
  612. if nochg_t[i] < min_val then
  613. min_val = nochg_t[i]
  614. end
  615. if nochg_t[i] > max_val then
  616. max_val = nochg_t[i]
  617. end
  618. end
  619. -- 计算平均值:去掉一个最大值和一个最小值
  620. local count = #nochg_t
  621. local avg
  622. -- 当窗口大小小于3时,无法去掉最大最小值
  623. if count < 3 then
  624. avg = totv // count
  625. else
  626. avg = (totv - min_val - max_val) // (count - 2)
  627. end
  628. log.info("append_vadc", totv, count, avg)
  629. return avg
  630. end
  631. -- 获取电池电压
  632. local function check_battery(param)
  633. adc.open(adc.CH_VBAT) -- 打开ADC通道
  634. local vbat = adc.get(adc.CH_VBAT) -- 读取电压
  635. adc.close(adc.CH_VBAT) -- 关闭ADC通道
  636. -- -- 计算采集电压的平均值,去掉一个最大值和一个最小值
  637. -- vbat = append_vadc(vbat)
  638. if param then
  639. vbat = math.floor(vbat / param + 0.5)
  640. end
  641. return vbat
  642. end
  643. --[[
  644. 获取充电系统状态信息(必须在task中运行,最大阻塞时间(包括超时重试时间)大概为20s)。该函数用于获取当前充电系统的完整状态,包括电池电压、充电阶段、充电状态、电池在位状态、充电器在位状态以及IC过热状态等信息。其中充电器是否在位,中断触发,触发回调事件为CHARGER_STATE_EVENT,附带的参数 true表示充电器在位,false表示充电器不在位。
  645. @api exchg.status()
  646. @return table 状态信息表
  647. {
  648. result = boolean, -- true: 成功, false: 失败
  649. vbat_voltage = number, -- 电池电压值(单位:mV),特殊值含义:
  650. -- -1: 当前阶段不需要测量
  651. -- -2: 电压测量失败
  652. -- -3: 仅充电器就绪(无电池)
  653. charge_stage = number, -- 当前充电阶段描述,可能值:
  654. -- 0 : 放电模式
  655. -- 1 : 预充电模式
  656. -- 2 : 涓流充电
  657. -- 3 : 恒流快速充电
  658. -- 4 : 预留状态
  659. -- 5 : 恒压快速充电
  660. -- 6 : 预留状态
  661. -- 7 : 充电完成
  662. -- 8 : 未知状态
  663. charge_complete = boolean, -- true: 充电完成, false: 充电未完成
  664. battery_present = boolean, -- true: 电池在位, false: 电池不在位
  665. charger_present = boolean, -- true: 充电器在位, false: 充电器不在位
  666. ic_overheat = boolean -- true: 充电IC过热, false: 充电IC未过热
  667. }
  668. @usage
  669. local status = exchg.status()
  670. if status.result then
  671. log.info("电池电压:", status.vbat_voltage,
  672. "充电阶段:", status.charge_stage,
  673. "充电是否完成:", status.charge_complete,
  674. "电池在位:", status.battery_present,
  675. "充电器在位:", status.charger_present,
  676. "IC过热:", status.ic_overheat)
  677. end
  678. --]]
  679. function exchg.status()
  680. -- 初始化所有状态
  681. local status = {
  682. result = true,
  683. vbat_voltage = 0,
  684. charge_stage = 8,
  685. charge_complete = false,
  686. battery_present = false,
  687. charger_present = is_charge,
  688. ic_overheat = false,
  689. }
  690. -- 1. 检查电池是否在位
  691. status.battery_present = check_battery_exists()
  692. sys.wait(100) -- yhm27xx操作之间必须延时一段时间
  693. -- 2. 检查充电IC是否过热
  694. local overheat_success, overheat_temp = check_over_heat()
  695. if overheat_success then
  696. status.ic_overheat = overheat_temp
  697. else
  698. log.warn("充电IC温度检测失败")
  699. status.result = false
  700. end
  701. sys.wait(100) -- yhm27xx操作之间必须延时一段时间
  702. -- 3. 获取充电阶段
  703. local stage_temp = get_charge_status()
  704. if stage_temp then
  705. status.charge_stage = stage_temp
  706. else
  707. log.warn("充电阶段检测失败")
  708. status.result = false
  709. end
  710. -- 4. 在特定阶段测量电池电压
  711. if status.battery_present then
  712. if charger_only then
  713. local result = pm.chgcmd(gpio_pin, sensor_addr, V_ctrl_register, voltage_setting)
  714. charger_only = false
  715. end
  716. -- 预充电或涓流充电阶段不测量电压
  717. if status.charge_stage == 1 or status.charge_stage == 2 then
  718. -- 设置特殊电压值表示未测量
  719. status.vbat_voltage = -1
  720. log.info("当前阶段:", status.charge_stage, "当前电压过低,正在涓流充电阶段努力充电中..., 请保持充电器连接")
  721. -- 恒流/恒压阶段,打开电压跟随功能测量电池电压
  722. elseif status.charge_stage == 3 or status.charge_stage == 5 then
  723. -- 打开电压跟随功能
  724. local result = set_sys_track(true)
  725. if result then
  726. local vbat = check_battery(1.053)
  727. status.vbat_voltage = vbat
  728. sys.wait(100)
  729. set_sys_track(false) -- 测量完关闭电压跟随功能
  730. else
  731. log.warn("无法打开电压跟随功能")
  732. -- 测量失败
  733. status.vbat_voltage = -2
  734. status.result = false
  735. end
  736. -- 充电完成
  737. elseif status.charge_stage == 7 then
  738. local vbat = check_battery(1.03)
  739. status.vbat_voltage = vbat
  740. -- 放电模式
  741. elseif status.charge_stage == 0 then
  742. local vbat = check_battery()
  743. status.vbat_voltage = vbat
  744. else
  745. -- 只做提示,不测量电压
  746. status.vbat_voltage = -1
  747. end
  748. elseif status.charger_present then
  749. -- 充电器在位,电池不在位, Vreg设置为4V,这样Vsys=1.03*4.0V=4.1V, 在模组的电压舒适区
  750. local result = pm.chgcmd(gpio_pin, sensor_addr, V_ctrl_register, set_4V)
  751. if not result then
  752. log.warn("仅充电器在位时, Vreg设置为4V失败")
  753. status.result = false
  754. end
  755. -- 标记当前状态为"充电器在位但电池不在位"
  756. charger_only = true
  757. -- 更新状态值
  758. status.vbat_voltage = -3
  759. status.charge_stage = 8
  760. status.charge_complete = false
  761. status.battery_present = false
  762. end
  763. -- 5. 判断充电是否完成
  764. status.charge_complete = (status.charge_stage == 7)
  765. return status
  766. end
  767. -- sys.taskInit(function()
  768. -- -- 连续ADC 电路稳定后,连续采集10次作为初始均值成员
  769. -- for i = 1, AVR_MAX do
  770. -- check_battery()
  771. -- sys.wait(200)
  772. -- end
  773. -- end)
  774. return exchg