/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file ht24lc02_eeprom.c * @author Motor Control SDK Team, Yuwell Software XiangenWang * @brief Voice Recognition Module Initialization Section, including peripheral initialization and message node insertion, etc. * @version 1.0 * @changelog version 1.0 初始版本 2025.11.17 - 新增:新建第一个版本的软件,待完善解析命令后的程序执行部分 ****************************************************************************** * @attention * *

© Copyright (c) 2025 Yuwell Software Danyang.Jiangsu.China. * All rights reserved.

* * Redistribution and use in source and binary forms, with or without * modification, are permitted, provided that the following conditions are met: * * 1. Redistribution of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of Yuwell Software nor the names of other * contributors to this software may be used to endorse or promote products * derived from this software without specific written permission. * 4. This software, including modifications and/or derivative works of this * software, must execute solely and exclusively on microcontroller or * microprocessor devices manufactured by or for Yuwell Software. * 5. Redistribution and use of this software other than as permitted under * this license is void and will automatically terminate your rights under * this license. * * THIS SOFTWARE IS PROVIDED BY Yuwell Software AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT * SHALL Yuwell Software OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* USER CODE END Header */ #include "ht24lc02_eeprom.h" #define HT24LC02_SDA_SET() FL_GPIO_SetOutputPin(EEPROM_24LC02_SDA_GROUP,EEPROM_24LC02_SDA_PIN) #define HT24LC02_SCL_SET() FL_GPIO_SetOutputPin(EEPROM_24LC02_SCL_GROUP,EEPROM_24LC02_SCL_PIN) #define HT24LC02_SDA_CLR() FL_GPIO_ResetOutputPin(EEPROM_24LC02_SDA_GROUP,EEPROM_24LC02_SDA_PIN) #define HT24LC02_SCL_CLR() FL_GPIO_ResetOutputPin(EEPROM_24LC02_SCL_GROUP,EEPROM_24LC02_SCL_PIN) #define EEPROM_WP_DISABLE() FL_GPIO_ResetOutputPin(EEPROM_24LC02_WP_GROUP,EEPROM_24LC02_WP_PIN) #define EEPROM_WP_ENABLE() FL_GPIO_SetOutputPin(EEPROM_24LC02_WP_GROUP,EEPROM_24LC02_WP_PIN) // 存储每个uint32_t数据的起始地址数组(共64个元素,索引0~63对应块0~块63) const uint8_t eeprom_start_addr[BLOCK_NUM] = { 0x00, 0x04, 0x08, 0x0C, // 块0~块3:地址0x00~0x0F(前16字节) 0x10, 0x14, 0x18, 0x1C, // 块4~块7:地址0x10~0x1F 0x20, 0x24, 0x28, 0x2C, // 块8~块11:地址0x20~0x2F 0x30, 0x34, 0x38, 0x3C, // 块12~块15:地址0x30~0x3F 0x40, 0x44, 0x48, 0x4C, // 块16~块19:地址0x40~0x4F 0x50, 0x54, 0x58, 0x5C, // 块20~块23:地址0x50~0x5F 0x60, 0x64, 0x68, 0x6C, // 块24~块27:地址0x60~0x6F 0x70, 0x74, 0x78, 0x7C, // 块28~块31:地址0x70~0x7F 0x80, 0x84, 0x88, 0x8C, // 块32~块35:地址0x80~0x8F 0x90, 0x94, 0x98, 0x9C, // 块36~块39:地址0x90~0x9F 0xA0, 0xA4, 0xA8, 0xAC, // 块40~块43:地址0xA0~0xAF 0xB0, 0xB4, 0xB8, 0xBC, // 块44~块47:地址0xB0~0xBF 0xC0, 0xC4, 0xC8, 0xCC, // 块48~块51:地址0xC0~0xCF 0xD0, 0xD4, 0xD8, 0xDC, // 块52~块55:地址0xD0~0xDF 0xE0, 0xE4, 0xE8, 0xEC, // 块56~块59:地址0xE0~0xEF 0xF0, 0xF4, 0xF8, 0xFC // 块60~块63:地址0xF0~0xFF(最后16字节) }; typedef enum { ACK, NACK }TYPE_ACK; typedef enum { INPUT, OUTPUT }SDA_MODE; /************************************************** 静态子函数定义 **************************************************/ /** * @brief 延时以适配I2C时序 * @param us 需要延时的微秒数 * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static void i2c_delay_us(uint32_t us) { FL_DelayUs(us); // 需根据MCU主频校准,确保实际延时≈1us } /** * @brief SDA引脚输入或输出模式配置 * @param NONE * @retval NONE * @note 为什么开漏输出无法读到数据呢? * 真是让人头大 * @author 王向恩 * @data 2025.11.19 */ static void i2c_sda_mode_set(SDA_MODE mode) { FL_GPIO_InitTypeDef GPIO_InitStruct; if(mode == INPUT) { GPIO_InitStruct.pin = EEPROM_24LC02_SDA_PIN; GPIO_InitStruct.mode = FL_GPIO_MODE_INPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_OPENDRAIN; GPIO_InitStruct.pull = FL_DISABLE; GPIO_InitStruct.remapPin = FL_DISABLE; GPIO_InitStruct.analogSwitch = FL_DISABLE; FL_GPIO_Init(EEPROM_24LC02_SDA_GROUP, &GPIO_InitStruct); }else{ GPIO_InitStruct.pin = EEPROM_24LC02_SDA_PIN; // 每次发送接收之前我还得把IO口初始化? GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; // 那我要开漏输出何用? GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_OPENDRAIN; GPIO_InitStruct.pull = FL_DISABLE; GPIO_InitStruct.remapPin = FL_DISABLE; GPIO_InitStruct.analogSwitch = FL_DISABLE; FL_GPIO_Init(EEPROM_24LC02_SDA_GROUP, &GPIO_InitStruct); } } /** * @brief 发送起始条件(SDA高→低,SCL保持高) * @param NONE * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static void i2c_start(void) { i2c_sda_mode_set(OUTPUT); HT24LC02_SDA_SET(); HT24LC02_SCL_SET(); i2c_delay_us(1); HT24LC02_SDA_CLR(); i2c_delay_us(1); HT24LC02_SCL_CLR(); i2c_delay_us(1); } /** * @brief 发送停止条件(SDA低→高,SCL保持高) * @param NONE * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static void i2c_stop(void) { i2c_sda_mode_set(OUTPUT); HT24LC02_SDA_CLR(); HT24LC02_SCL_CLR(); i2c_delay_us(1); // 满足t_SU:STO最小250ns HT24LC02_SCL_SET(); i2c_delay_us(1); // 满足t_SU:STO最小250ns HT24LC02_SDA_SET(); i2c_delay_us(1); } /** * @brief 发送1字节数据 * @param dat 要发送的数据 * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static void i2c_sendByte(uint8_t dat) { uint8_t i; i2c_sda_mode_set(OUTPUT); for(i = 0; i < 8; i++) { HT24LC02_SCL_CLR(); i2c_delay_us(1); if(dat & 0x80) { HT24LC02_SDA_SET(); }else{ HT24LC02_SDA_CLR(); } i2c_delay_us(1); // 满足t_SU:DAT最小100ns HT24LC02_SCL_SET(); i2c_delay_us(1); // 满足t_HIGH最小400ns dat <<= 1; } HT24LC02_SCL_CLR(); i2c_delay_us(1); } /** * @brief 接收1字节数据 * @param ack 接受一个字节的数据 * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static uint8_t i2c_recvByte(TYPE_ACK ack) { uint8_t dat = 0; uint32_t EEcount = 0; i2c_sda_mode_set(OUTPUT); HT24LC02_SDA_SET(); // 释放SDA i2c_sda_mode_set(INPUT); for(uint8_t i=0; i<8; i++) { HT24LC02_SCL_CLR(); i2c_delay_us(1); HT24LC02_SCL_SET(); i2c_delay_us(5); // 满足t_AA最大600ns dat <<= 1; if(FL_GPIO_GetInputPin(EEPROM_24LC02_SDA_GROUP,EEPROM_24LC02_SDA_PIN)) { dat |= 0x01; EEcount++; } } HT24LC02_SCL_CLR(); i2c_delay_us(1); i2c_sda_mode_set(OUTPUT); // 发送ACK/NACK if(ack == ACK) { HT24LC02_SDA_CLR(); }else{ HT24LC02_SDA_SET(); } HT24LC02_SCL_SET(); i2c_delay_us(1); HT24LC02_SCL_CLR(); i2c_delay_us(1); return dat; } /** * @brief 等待从机ACK * @param NONE * @retval NONE * @note * @author 王向恩 * @data 2025.11.19 */ static TYPE_ACK i2c_waitAck(void) { TYPE_ACK ack = NACK; i2c_sda_mode_set(OUTPUT); HT24LC02_SDA_SET(); // 释放SDA HT24LC02_SCL_CLR(); i2c_delay_us(1); HT24LC02_SCL_SET(); i2c_delay_us(1); i2c_sda_mode_set(INPUT); if(!FL_GPIO_GetInputPin(EEPROM_24LC02_SDA_GROUP,EEPROM_24LC02_SDA_PIN)) { ack = ACK; // 检测到低电平为ACK } HT24LC02_SCL_CLR(); i2c_delay_us(1); return ack; } /************************************************** 外部API函数函数定义 **************************************************/ /** * @brief 将多个字节数据写入到EEPROM中 * @param data 要写入数据的起始地址 addr 芯片存储数据的地址 len 数据总长度 * @retval NONE * @note 注意EEPROM使用规则 不可跨页写入 * @author 王向恩 * @data 2025.11.19 */ static void ht24lc02_multiByte_write(uint8_t* data,uint16_t addr,uint8_t len) { uint8_t i; if((addr + len) > HT24LC02_ADDR_MAX) { return; } i2c_start(); i2c_sendByte(HT24LC02_ADDR | 0x00); // 写命令(0x00) if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,释放总线 return; } i2c_sendByte(addr & 0xFF); if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,释放总线 return; } for(i = 0;i < len;i++) { i2c_sendByte(data[i]); if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,释放总线 return; } } i2c_stop(); i2c_delay_us(5000); } /** * @brief 将多个字节数据读取 * @param data 要读取数据的起始地址 addr 芯片存储数据的地址 len 数据总长度 * @retval NONE * @note 注意EEPROM使用规则 不可跨页读取 * @author 王向恩 * @data 2025.11.19 */ static void ht24lc02_multiByte_read(uint8_t* data,uint16_t addr,uint8_t len) { uint8_t i; if((addr + len) > HT24LC02_ADDR_MAX) { return; } i2c_start(); // 发送器件地址+写命令(先指定要读取的“专属地址”) i2c_sendByte(HT24LC02_ADDR | 0x00); if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,发送Stop释放总线 return; } i2c_sendByte(addr); if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,释放总线 return; } i2c_start(); // 重复起始条件,切换为读命令 i2c_sendByte(HT24LC02_ADDR | 0x01); // 读命令(0x01) if (i2c_waitAck()) { i2c_stop(); // 未收到ACK,释放总线 return; } for(i = 0; i < len; i++) { if(i == (len-1)) { data[i] = i2c_recvByte(NACK); }else{ data[i] = i2c_recvByte(ACK); } } i2c_stop(); } /** * @brief 将4个字节数据写入到EEPROM中 * @param word 要写入数据 addr_index 芯片存储数据的地址 * @retval NONE * @note 注意EEPROM使用规则 不可跨页写入 * @author 王向恩 * @data 2025.11.19 */ void ht24lc02_word_write(uint32_t word, uint8_t addr_index) { uint8_t data[4]; uint8_t i; for(i = 0; i < 4; i++) { data[3-i] = (word >> (i*8)) & 0xFF; } ht24lc02_multiByte_write(data, eeprom_start_addr[addr_index], 4); } /** * @brief 将4个字节数据读取到外部 * @param data 要读取数据的起始地址 addr 芯片存储数据的地址 * @retval NONE * @note 注意EEPROM使用规则 不可跨页读取 * @author 王向恩 * @data 2025.11.19 */ static void ht24lc02_word_read(uint32_t* ptr_word, uint8_t addr_index) { uint8_t data[4]; uint32_t world = 0;; uint8_t i; ht24lc02_multiByte_read(data, eeprom_start_addr[addr_index], 4); for(i = 0; i < 4; i++) { world += (data[3-i] << (i*8)); } *ptr_word = world; } /** * @brief 获取总累时 小时数 * @param NONE * @retval 返回uint32_t 类型的四个字节的累时数据 * @note NONE * @author 王向恩 * @data 2025.11.19 */ static uint32_t ht24lc02_get_total_hours(void) { uint32_t totalTime = 0; ht24lc02_word_read(&totalTime, INDEX_TOTAL_HOURS); return totalTime; } /** * @brief 获取总累时 分钟数 * @param NONE * @retval 返回uint32_t 类型的四个字节的累时数据 * @note NONE * @author 王向恩 * @data 2025.11.19 */ static uint32_t ht24lc02_get_total_minutes(void) { uint32_t totalTime = 0; ht24lc02_word_read(&totalTime, INDEX_TOTAL_MINUTES); return totalTime; } /** * @brief 设定总累时 小时数 * @param 设置uint32_t 类型的四个字节的累时数据 * @retval NONE * @note NONE * @author 王向恩 * @data 2025.11.19 */ static void ht24lc02_set_total_hours(uint32_t hours) { if(hours > 19999) { hours = 19999; } ht24lc02_word_write(hours, INDEX_TOTAL_HOURS); } /** * @brief 设定总累时 分钟数 * @param 设置uint32_t 类型的四个字节的累时数据 * @retval NONE * @note NONE * @author 王向恩 * @data 2025.11.19 */ static void ht24lc02_set_total_minutes(uint32_t minutes) { ht24lc02_word_write(minutes, INDEX_TOTAL_MINUTES); } /** * @brief 根据地址获取四个字节数据 * @param 数据存储地址的索引值 * @retval 存储的uint16_t数据 * @note NONE * @author 王向恩 * @data 2025.12.03 */ static uint32_t ht24lc02_get_uint32_by_index(uint8_t addr_index) { uint32_t ret = 0; uint8_t data[4]; uint8_t i; ht24lc02_multiByte_read(data, eeprom_start_addr[addr_index], 4); for(i = 0; i < 4; i++) { ret += (data[3-i] << (i*8)); } return ret; } /************************** 对外导出的API函数 *************************/ /** * @brief 初始化EERPOM对应的硬件并向队列中下发一个当前累时数据 * @param NONE * @retval NONE * @note NONE * @author 王向恩 * @data 2025.11.25 */ void eeprom_ht24lc02_init(void) { FL_GPIO_InitTypeDef GPIO_InitStruct; MsgQueueItem queue_item; MsgQueueItem filter_item; GPIO_InitStruct.pin = EEPROM_24LC02_WP_PIN; GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.pull = FL_ENABLE; GPIO_InitStruct.remapPin = FL_DISABLE; GPIO_InitStruct.analogSwitch = FL_DISABLE; FL_GPIO_Init(EEPROM_24LC02_WP_GROUP, &GPIO_InitStruct); // 配置WP为输出模式 GPIO_InitStruct.pin = EEPROM_24LC02_SCL_PIN; FL_GPIO_Init(EEPROM_24LC02_SCL_GROUP, &GPIO_InitStruct); // 配置SCL为输出模式 GPIO_InitStruct.pin = EEPROM_24LC02_SDA_PIN; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_OPENDRAIN; FL_GPIO_Init(EEPROM_24LC02_SDA_GROUP, &GPIO_InitStruct); // 配置SDA为开漏输出模式 // 向当前队列中插入包含累时的信息 queue_item.type = MSG_TYPE_TIME_SEND; queue_item.data.hour_meter.hour = ht24lc02_get_total_hours(); // 获取当前累时 小时数 queue_item.data.hour_meter.minute = ht24lc02_get_total_minutes(); // 获取当前累时 分钟数 insert_queue_node_in_head(&global_queue, queue_item); // 向当前队列中插入包含报警点的信息 queue_item.type = MSG_TYPE_AD_BOUNDARY_SEND; queue_item.data.adc_boundary.elec_low_boundary = EEPROM_E3_ELEC_LOW_VALUE; // E3 电流过小,通过程序给定 queue_item.data.adc_boundary.elec_high_boundary = ht24lc02_get_uint32_by_index(INDEX_E4_ELEC_HIGH); // E4 电流过大读取EEPROM中的值 queue_item.data.adc_boundary.ntc_low_boundary = 0; // NTC太低不管他 给他随意值 queue_item.data.adc_boundary.ntc_high_boundary = EEPROM_E5_NTC_HIGH_VALUE; // E5 通过程序给个值 queue_item.data.adc_boundary.press_low_boundary = ht24lc02_get_uint32_by_index(INDEX_E1_PRESS_LOW); // E1 读取EEPROM中标定的值 queue_item.data.adc_boundary.press_high_boundary = ht24lc02_get_uint32_by_index(INDEX_E2_PRESS_HIGH); // E2 读取EEPROM中标定的值 queue_item.data.adc_boundary.net220v_low_boundary = ht24lc02_get_uint32_by_index(INDEX_E7_220V_LOW); // E7 读取EEPROM中的值 queue_item.data.adc_boundary.net220v_high_boundary = 0; // 不使用 随便给个值 insert_queue_node_in_head(&global_queue, queue_item); // 判定是否曾发生过清洁/更换滤清器事件 filter_item.type = MSG_TYPE_FILTER_SERVER; filter_item.data.filter_server.last_clean_filter_hours = ht24lc02_get_uint32_by_index(INDEX_LAST_CLEAN_FILTER_HOURS); filter_item.data.filter_server.last_replace_filter_hours = ht24lc02_get_uint32_by_index(INDEX_LAST_REPLACE_FILTER_HOURS); filter_item.data.filter_server.clean_filter_time_need_stored = false; filter_item.data.filter_server.replace_filter_time_need_stored = false; insert_queue_node_in_head(&global_queue, filter_item); } /** * @brief 根据队列内容向EEPROM中存入当前小时数或分钟数 * @param NONE * @retval NONE * @note 放入定时器任务中周期性执行 * @author 王向恩 * @data 2025.11.25 */ void eeprom_ht24lc02_save_task(MessageQueue* queue) { uint32_t hours = 0; uint32_t minutes = 0; MsgQueueItem queue_item; MsgQueueItem adc_item; MsgQueueItem carlib_item; MsgQueueItem filter_item; bool result = false; result = remove_queue_node_by_type(&global_queue, MSG_TYPE_HOUR_SAVE, &queue_item); // 取出队列中对应类型的数据 if(result) { hours = queue_item.data.hour_meter.hour; ht24lc02_set_total_hours(hours); } result = remove_queue_node_by_type(&global_queue, MSG_TYPE_MINUTE_SAVE, &queue_item); // 取出队列中对应类型的数据 if(result) { minutes = queue_item.data.hour_meter.minute; ht24lc02_set_total_minutes(minutes); } // 检测是否发生464事件 if(xOxygenEventGroupCheckBit(&global_event, EVENT_464_CLEAR)) { // 清空该事件并下发蓝色灯慢闪事件 vOxygenEventGroupClearBits(&global_event, EVENT_464_CLEAR); // 此处放置清空EEPROM累时的代码 ht24lc02_set_total_minutes(0); ht24lc02_set_total_hours(0); vOxygenEventGroupSetBits(&global_event, EVENT_BLUE_BLINK); } // 检测是否需要存储上次清理/更换滤清器的时间 if(remove_queue_node_by_type(&global_queue, MSG_TYPE_FILTER_TIME_SAVE, &filter_item)) { if(filter_item.data.filter_server.clean_filter_time_need_stored) { ht24lc02_word_write(filter_item.data.filter_server.last_clean_filter_hours, INDEX_LAST_CLEAN_FILTER_HOURS); filter_item.data.filter_server.clean_filter_time_need_stored = false; } if(filter_item.data.filter_server.replace_filter_time_need_stored) { ht24lc02_word_write(filter_item.data.filter_server.last_replace_filter_hours, INDEX_LAST_REPLACE_FILTER_HOURS); filter_item.data.filter_server.clean_filter_time_need_stored = false; } } // 读取E1 E2 E4 E7边界值并存储 peek_queue_node_by_type(&global_queue, MSG_TYPE_AD_SAMPLING, &adc_item); // 读取队列中的ADC采样值 if(remove_queue_node_by_type(&global_queue, MSG_TYPE_CARLIB_SAVE, &carlib_item)) // 取出队列中对应类型的数据 { switch(carlib_item.data.carlib_data.netcode) { case NETCODE_E1_CALIB: ht24lc02_word_write(adc_item.data.adc_data.real_press, INDEX_E1_PRESS_LOW); // 闪烁OK灯标识存储完毕 vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK); break; case NETCODE_E2_CALIB: ht24lc02_word_write(adc_item.data.adc_data.real_press, INDEX_E2_PRESS_HIGH); // 闪烁OK灯标识存储完毕 vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK); break; case NETCODE_E4_CALIB: ht24lc02_word_write(adc_item.data.adc_data.real_elec, INDEX_E4_ELEC_HIGH); // 闪烁OK灯标识存储完毕 vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK); break; case NETCODE_E7_CALIB: // 需要存储E7边界数值 ht24lc02_word_write(adc_item.data.adc_data.real_220V, INDEX_E7_220V_LOW); // 闪烁OK灯标识存储完毕 vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK); break; default: break; } } } /************************ (C) COPYRIGHT YUWELL *****END OF FILE****/