current_v/Src/bs83b12_capacitive_touch.c
2025-12-31 08:21:43 +08:00

653 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file bs83b12_capacitive_touch.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.13
- 新增:新建第一个版本的软件,待完善解析命令后的程序执行部分
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2025 Yuwell Software Danyang.Jiangsu.China.
* All rights reserved.</center></h2>
*
* 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 "bs83b12_capacitive_touch.h"
#define SDA_SET BS83B12_SDA_SET
#define SCL_SET BS83B12_SCL_SET
#define SDA_CLEAR BS83B12_SDA_CLEAR
#define SCL_CLEAR BS83B12_SCL_CLEAR
typedef enum
{
ACK,
NACK
}TYPE_ACK;
typedef enum
{
INPUT,
OUTPUT
}SDA_MODE;
/************************** 仅供内容访问的IO控制封装函数 *************************/
/**
* @brief 微秒延时
* @param us 需要延时的微秒数
* @retval NONE
* @note 根据MCU主频调整确保时序满足HT16K33要求
* @author jarvis
* @data 2025.11.25
*/
static void I2C_Delay_us(uint32_t us)
{
FL_DelayUs(us);
}
/**
* @brief I2C起始条件SCL高电平时SDA从高变低
* @param NONE
* @retval NONE
* @note 根据飞利浦I2C官方文档编写
* @author jarvis
* @data 2025.11.25
*/
static void I2C_Start(void)
{
SDA_SET();
SCL_SET();
I2C_Delay_us(1);
SDA_CLEAR();
I2C_Delay_us(1);
SCL_CLEAR();
}
/**
* @brief I2C停止条件SCL高电平时SDA从低变高
* @param NONE
* @retval NONE
* @note 根据飞利浦I2C官方文档编写停止后释放I2C总线控制权
* @author jarvis
* @date 2025.11.25
*/
static void I2C_Stop(void)
{
SDA_CLEAR();
SCL_SET();
I2C_Delay_us(1);
SDA_SET();
I2C_Delay_us(1);
}
/**
* @brief I2C接收应答信号
* @param NONE
* @retval uint8_t - 应答检测结果0表示接收到应答(ACK)1表示未接收到应答(NACK)
* @note SDA引脚需配置为输入模式通过检测PC11引脚电平判断应答状态遵循I2C协议时序
* @author jarvis
* @date 2025.11.25
*/
static uint8_t I2C_ReceiveAck(void)
{
uint8_t ack = 1;
SCL_CLEAR();
SDA_SET(); // 释放SDA总线
I2C_Delay_us(1);
SCL_SET();
I2C_Delay_us(1);
if((FL_GPIO_ReadInputPort(BS83B12_SDA_GPIO_GROUP)&BS83B12_SDA_GPIO_PIN) == 0)
{
ack = 0; // 检测到应答
}
SCL_CLEAR();
return ack;
}
/**
* @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 = BS83B12_SDA_GPIO_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(BS83B12_SDA_GPIO_GROUP, &GPIO_InitStruct);
}else{
GPIO_InitStruct.pin = BS83B12_SDA_GPIO_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(BS83B12_SDA_GPIO_GROUP, &GPIO_InitStruct);
}
}
/**
* @brief I2C发送一个字节数据
* @param data - 待发送的8位数据
* @retval NONE
* @note 高位先行(MSB first)遵循I2C协议时序SCL高电平时数据有效
* @author jarvis
* @date 2025.11.25
*/
static void I2C_SendByte(uint8_t data)
{
uint8_t i;
SCL_CLEAR();
for(i = 0; i < 8; i++)
{
if(data & 0x80)
{
SDA_SET(); // 发送最高位
}else{
SDA_CLEAR();
}
data <<= 1;
I2C_Delay_us(1);
SCL_SET();
I2C_Delay_us(1);
SCL_CLEAR();
}
}
/**
* @brief I2C读取一个字节数据并发送应答/非应答
* @param ack - 应答类型ACK:发送应答, NACK:发送非应答)
* @retval 读取到的8位数据
* @note 高位先行(MSB first)遵循I2C协议时序SCL高电平时采样SDA数据
* 读取完成后根据ack参数发送ACK/NACK位用于控制多字节读取时序
* @author jarvis
* @date 2025.12.05
*/
static uint8_t I2C_ReadByte(TYPE_ACK ack)
{
uint8_t dat = 0;
uint32_t EEcount = 0;
i2c_sda_mode_set(OUTPUT);
SDA_SET(); // 释放SDA
i2c_sda_mode_set(INPUT);
for(uint8_t i=0; i<8; i++)
{
SCL_CLEAR();
I2C_Delay_us(1);
SCL_SET();
I2C_Delay_us(5); // 满足t_AA最大600ns
dat <<= 1;
if(FL_GPIO_GetInputPin(BS83B12_SDA_GPIO_GROUP,BS83B12_SDA_GPIO_PIN))
{
dat |= 0x01;
EEcount++;
}
}
SCL_CLEAR();
I2C_Delay_us(1);
i2c_sda_mode_set(OUTPUT);
SDA_CLEAR();
// 发送ACK/NACK
if(ack == ACK)
{
SDA_CLEAR();
}else{
SDA_SET();
}
SCL_SET();
I2C_Delay_us(1);
SCL_CLEAR();
I2C_Delay_us(1);
return dat;
}
/**
* @brief 读取BS83B12电容触摸芯片的3字节数据
* @param feedback_data - 存储读取数据的数组指针需至少3字节空间
* @retval NONE
* @note 遵循I2C协议先写地址再切换为读模式最后一字节发送NACK前两字节发送ACK
* @author jarvis
* @date 2025.12.05
*/
static void bs83b12_read_byte(uint8_t* feedback_data)
{
I2C_Start();
// 发送器件地址+写命令(先指定要读取的“专属地址”)
I2C_SendByte(BS83B12_ADDR | 0x00);
if (I2C_ReceiveAck())
{
I2C_Stop(); // 未收到ACK发送Stop释放总线
return;
}
I2C_SendByte(0x00);
if (I2C_ReceiveAck())
{
I2C_Stop(); // 未收到ACK释放总线
return;
}
I2C_Start(); // 重复起始条件,切换为读命令
I2C_SendByte(BS83B12_ADDR | 0x01); // 读命令0x01
if (I2C_ReceiveAck())
{
I2C_Stop(); // 未收到ACK释放总线
return;
}
for(uint8_t i = 0;i < 3;i++)
{
if(i == 2)
{
feedback_data[i] = I2C_ReadByte(NACK);
}else{
feedback_data[i] = I2C_ReadByte(ACK);
}
}
I2C_Stop();
return;
}
/**
* @brief 向BS83B12电容触摸芯片写入指定长度的字节数据
* @param send_data - 待发送数据的数组指针
* @param len - 待发送数据的字节长度
* @retval NONE
* @note 遵循I2C写协议每发送一字节等待ACK若未收到ACK立即停止传输并释放总线
* @author jarvis
* @date 2025.12.05
*/
static void bs83b12_write_byte(uint8_t* send_data, uint8_t len)
{
I2C_Start();
I2C_SendByte(BS83B12_ADDR | 0x00);
if (I2C_ReceiveAck())
{
I2C_Stop(); // 未收到ACK发送Stop释放总线
return;
}
for(uint8_t i = 0;i < len;i++)
{
I2C_SendByte(send_data[i]);
if (I2C_ReceiveAck())
{
I2C_Stop(); // 未收到ACK释放总线
return;
}
}
I2C_Stop();
}
/**
* @brief BS83B12电容触摸芯片初始化函数
* @param NONE
* @retval NONE
* @note 初始化相关GPIO报警黄灯、I2C SCL/SDA为推挽输出模式
* 发送配置指令开启流量计背光灯
* @author jarvis
* @date 2025.12.05
*/
void bs83b12_capacitive_touch_init(void)
{
// 初始化黄灯所在GPIO
FL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.pin = ALARM_YELLOW_GPIO_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(ALARM_YELLOW_GPIO_PORT, &GPIO_InitStruct);
// 配置SCK为输出模式
GPIO_InitStruct.pin = BS83B12_SCL_GPIO_PIN;
FL_GPIO_Init(BS83B12_SCL_GPIO_GROUP, &GPIO_InitStruct);
// 配置SDA为输出模式
GPIO_InitStruct.pin = BS83B12_SDA_GPIO_PIN;
FL_GPIO_Init(BS83B12_SDA_GPIO_GROUP, &GPIO_InitStruct);
// 开启流量计背光灯
uint8_t config_backlight_on[2] = {BS83B12_BACK_LIGHT_ON_BYTE0, BS83B12_BACK_LIGHT_ON_BYTE1};
bs83b12_write_byte(config_backlight_on, 2); // 开启流量计背光
}
// 处理按键事件
static void process_121_event()
{
MsgQueueItem stm_item;
MsgQueueItem queue_item;
// 下发校准数据存储消息
queue_item.type = MSG_TYPE_CARLIB_SAVE;
if(xOxygenEventGroupCheckBit(&global_event, EVENT_KEY121_PRESS))
{
// 若发生121按键事件存储当前数据到EEPROM
peek_queue_node_by_type(&global_queue, MSG_TYPE_OXG_STM, &stm_item);
queue_item.data.carlib_data.netcode = stm_item.data.state_machine.data;
modify_or_add_queue_node_by_type(&global_queue, MSG_TYPE_CARLIB_SAVE, queue_item); // 存储当前值
vOxygenEventGroupClearBits(&global_event, EVENT_KEY121_PRESS);
}
}
/**
* @brief BS83B12触摸按键读取任务函数
* @param NONE
* @retval NONE
* @note 1. 读取触摸数据并校验有效性;
* 2. 检测KEY2单次按下事件并置位对应事件标志
* 3. 检测KEY1→KEY2→KEY1组合按键带超时重置机制完成后置位KEY121事件标志
* 4. 需在循环中调用超时时间由KEY121_TIMEOUT_TICKS定义
* @author jarvis
* @date 2025.12.05
*/
void bs83b12_touch_read_task(void)
{
static uint8_t s_key121_step = 0; // 1-2-1切换步骤0-初始1-已按key12-已按key2
static uint16_t s_key121_timeout_cnt = 0;
static uint8_t key2_filter_cnt = 0;
static uint8_t key2_trigger_lock = 0; // 按键触发锁1=已触发0=未触发
uint8_t read_touch[3] = {0};
uint8_t curr_key = BS83B12_KEY_NONE;
MsgQueueItem stm_item;
MsgQueueItem time_item;
peek_queue_node_by_type(&global_queue, MSG_TYPE_OXG_STM, &stm_item); // 获取当前状态机信息
bs83b12_read_byte(read_touch);
if(((~(read_touch[0]+read_touch[1]) + 1)&0xFF) == read_touch[2]) // 判定是否为有效按键
{
curr_key = read_touch[0];
}else
{
return;
}
if(((s_key121_step == 0)&&(curr_key == BS83B12_KEY2_CODE))&&\
((stm_item.data.state_machine.oxg_stm == STM_ERROR_SHOUNTDOWN)||(stm_item.data.state_machine.oxg_stm == STM_ERROR_NONE_STOP)))
{
key2_filter_cnt++;
if (key2_filter_cnt >= 1 && key2_trigger_lock == 0)
{
vOxygenEventGroupSetBits(&global_event, EVENT_KEY2_PRESS); // 标记当前发生了按下按键2的事件
key2_trigger_lock = 1; // 上锁
}
}else{
key2_trigger_lock = 0; // 按键滤波锁清空
key2_filter_cnt = 0;
}
// 检测key1→key2→key1切换带超时重置
switch(s_key121_step)
{
case 0: // 初始状态等待按下key1
// 超时计数器清零(空闲状态无需计时)
s_key121_timeout_cnt = 0;
if(curr_key == BS83B12_KEY1_CODE)
{
s_key121_step = 1; // 已按key1
}
break;
case 1: // 已按key1等待按下key2
// 超时检查
if(++s_key121_timeout_cnt >= KEY121_TIMEOUT_TICKS)
{
s_key121_step = 0; // 超时重置步骤
s_key121_timeout_cnt = 0; // 清零计数器
break;
}
// 状态迁移
if(curr_key == BS83B12_KEY2_CODE)
{
s_key121_step = 2; // 进入步骤2已按key2
s_key121_timeout_cnt = 0; // 重置超时计数器
}
break;
case 2: // 已按key2等待按下key1完成1-2-1
// 超时检查
if(++s_key121_timeout_cnt >= KEY121_TIMEOUT_TICKS)
{
s_key121_step = 0; // 超时重置步骤
s_key121_timeout_cnt = 0; // 清零计数器
break;
}
// 状态迁移
if(curr_key == BS83B12_KEY1_CODE)
{
s_key121_step = 0; // 重置步骤
s_key121_timeout_cnt = 0; // 清零计数器
if(stm_item.data.state_machine.oxg_stm == STM_CARLIB) // 只有在标定模式下 121事件才有效
{
vOxygenEventGroupSetBits(&global_event, EVENT_KEY121_PRESS); // 标记当前发生了按下按键121的事件
}
}
break;
default:
s_key121_step = 0;
s_key121_timeout_cnt = 0;
break;
}
switch(stm_item.data.state_machine.oxg_stm)
{
case STM_INIT:
if(peek_queue_node_by_type(&global_queue, MSG_TYPE_CURRENT_TIME, &time_item))
{
// 此部分代码放到了it文件中进行了实现
}
break;
case STM_ERROR_NONE_STOP: // 故障到停机状态时
ALARM_YELLOW_ON;
if(xOxygenEventGroupCheckAnyBits(&global_event, EVENT_O2_835_LOW | EVENT_E7_220V_LOW))
{
if(xOxygenEventGroupCheckBit(&global_event, EVENT_KEY2_PRESS)) // 若此时发生了按键标记
{
if(xOxygenEventGroupCheckBit(&global_event, EVENT_BEEP_MUTE))
{
vOxygenEventGroupClearBits(&global_event, EVENT_BEEP_MUTE);
}else{
vOxygenEventGroupSetBits(&global_event, EVENT_BEEP_MUTE); // 标记报警器静音
}
vOxygenEventGroupClearBits(&global_event, EVENT_KEY2_PRESS); // 清除按键2事件
}
}
break;
case STM_ERROR_SHOUNTDOWN: // 发生E7故障 显示E7但是不停机
ALARM_YELLOW_ON;
if(xOxygenEventGroupCheckBit(&global_event, EVENT_KEY2_PRESS)) // 若此时发生了按键标记
{
if(xOxygenEventGroupCheckBit(&global_event, EVENT_BEEP_MUTE))
{
vOxygenEventGroupClearBits(&global_event, EVENT_BEEP_MUTE);
}else{
vOxygenEventGroupSetBits(&global_event, EVENT_BEEP_MUTE); // 标记报警器静音
}
vOxygenEventGroupClearBits(&global_event, EVENT_KEY2_PRESS); // 清除按键2事件
}
break;
case STM_CARLIB: // 若当前为校准状态
ALARM_YELLOW_OFF;
vOxygenEventGroupClearBits(&global_event, EVENT_KEY2_PRESS); // 清除按键2事件
// 若当前发生121事件则将边界值写入对应边界
process_121_event();
break;
case STM_NOM:
vOxygenEventGroupClearBits(&global_event, EVENT_KEY2_PRESS); // 清除按键2事件
break;
default:
ALARM_YELLOW_OFF;
break;
}
}
/************************ (C) COPYRIGHT Yuwell *****END OF FILE****/