本系统使用多个GPIO引脚连接不同功能的按键,主要包括护理按键、增援按键、取消按键、呼叫按键、手柄呼叫、手柄换药和手柄拔针等功能。
在mapp_gpio.h
文件中,定义了按键输入引脚:
// 文件: source/user/mapp_gpio.h
#define KEY_PA7_EMEGENCY_DET GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) // 紧急按钮
#define KEY_PA4_HANDLE_DET GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) // 手柄检测
#define KEY_PA5_CHANGE_DET GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) // 换药按键
#define KEY_PB3_NURSE_DET GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) // 护理按键
#define KEY_PB5_CALL_DET GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) // 呼叫按键
#define KEY_PA15_REINFORCE_DET GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15) // 增援按键
#define KEY_PB4_CANCEL_DET GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) // 取消按键
语音输出相关的引脚定义:
// 文件: source/user/mapp_gpio.h
#define VOICE_PB15_ON GPIO_SetBits(GPIOB, GPIO_Pin_15) // 语音芯片数据线
#define VOICE_PB15_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_15)
#define VOICE_PF7_ON GPIO_SetBits(GPIOF, GPIO_Pin_7) // 语音通道控制
#define VOICE_PF7_OFF GPIO_ResetBits(GPIOF, GPIO_Pin_7)
#define VOICESW_H GPIO_SetBits(GPIOA, GPIO_Pin_0) // 语音通道
#define VOICESW_L GPIO_ResetBits(GPIOA, GPIO_Pin_0)
在mapp_key.c
文件的KeyTask
函数中,每35ms执行一次按键扫描,采集各个按键的状态:
// 文件: source/user/mapp_key.c
void KeyTask(void)
{
static U08 last_KeyVal[KEY_COUNT] = {0};
static U08 trascnt1[KEY_COUNT] = {0};
static U08 trascnt2[KEY_COUNT] = {0};
U08 index = 0;
U08 u8KeyVal[KEY_COUNT] = {0};
u8KeyVal[0] = !KEY_PB5_CALL_DET; // 呼叫按键
u8KeyVal[1] = !gtArm.PA4_Value; // 手柄检测
u8KeyVal[2] = KEY_PA7_EMEGENCY_DET; // 紧急按钮
u8KeyVal[3] = !KEY_PA15_REINFORCE_DET; // 增援按键
u8KeyVal[4] = !KEY_PB3_NURSE_DET; // 护理按键
u8KeyVal[5] = !KEY_PA5_CHANGE_DET; // 换药按键
u8KeyVal[6] = !KEY_PB4_CANCEL_DET; // 取消按键
// 按键状态处理...
}
在按键信号采集后,系统会进行按键去抖处理,只有连续多次采样状态一致才认为是有效按键。同时,按键状态会被存储在gtArm.KeyState
变量中。
gtArm.KeyState
是一个8位无符号整数(U08)变量,每一位对应一个按键的状态:
位 | 二进制值 | 十六进制值 | 对应按键 | 对应KEY变量 |
---|---|---|---|---|
位0 | 0x01 | 0x01 | 呼叫按键(KEY_PB5_CALL_DET) | KEY1 |
位1 | 0x02 | 0x02 | 手柄呼叫(KEY_PA4_HANDLE_DET) | KEY0 |
位2 | 0x04 | 0x04 | 紧急按钮(KEY_PA7_EMEGENCY_DET) | KEY3 |
位3 | 0x08 | 0x08 | 增援按键(KEY_PA15_REINFORCE_DET) | KEY5 |
位4 | 0x10 | 0x10 | 护理按键(KEY_PB3_NURSE_DET) | KEY4 |
位5 | 0x20 | 0x20 | 换药按键(KEY_PA5_CHANGE_DET) | KEY2 |
位6 | 0x40 | 0x40 | 取消按键(KEY_PB4_CANCEL_DET) | KEY3 |
位7 | 0x80 | 0x80 | 未使用 | 无 |
当对应位的值为1时,表示该按键被按下;值为0时,表示按键未被按下。
注意:位2(0x04)和位6(0x40)都映射到同一个KEY3变量,这可能会导致功能混淆。
对于每个按键,都进行如下处理:
// 文件: source/user/mapp_key.c
for (index = 0; index < KEY_COUNT; index++) {
if (u8KeyVal[index] == 0) { // 没有按下
gtArm.KeyState &= ~(1 << index); // 清除对应位,表示按键未按下
trascnt1[index] = 0;
trascnt2[index]++;
s_keytime[index] = 0;
if (trascnt2[index] > SCAN_ACK_COUNT) { // 防抖处理
trascnt2[index] = 0;
if (last_KeyVal[index] != u8KeyVal[index]) {
// 处理按键释放事件
OSQPost(AmiMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_RELEASED));
last_KeyVal[index] = u8KeyVal[index];
}
}
} else { // 按键按下
gtArm.KeyState |= (1 << index); // 设置对应位为1,表示按键按下
trascnt1[index]++;
trascnt2[index] = 0;
if (trascnt1[index] > SCAN_ACK_COUNT) { // 防抖处理
trascnt1[index] = 0;
if (s_keytime[index] > OS_TIMER_1S5) { // 长按判断
if (last_KeyVal[index] != KEY_CONTINUE) {
last_KeyVal[index] = KEY_CONTINUE;
}
} else if (last_KeyVal[index] != u8KeyVal[index]) {
// 处理按键按下事件
OSQPost(AmiMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_PRESSED));
last_KeyVal[index] = u8KeyVal[index];
}
}
}
}
c
// 文件: source/user/mapp_key.c
gtArm.KeyState &= ~(1 << index);
这行代码执行以下操作:
(1 << index)
创建一个只有第index位为1的掩码~(1 << index)
对掩码取反,创建一个除第index位外都为1的掩码gtArm.KeyState &= ~(1 << index)
使用按位与操作,保留gtArm.KeyState中除了第index位之外的所有位,而将第index位清零例如,当index=3时(增援按键),操作过程为:
(1 << 3)
= 0x08~(1 << 3)
= 0xF7 (二进制: 11110111)gtArm.KeyState &= 0xF7
将gtArm.KeyState的第3位清零,其他位保持不变c
// 文件: source/user/mapp_key.c
gtArm.KeyState |= (1 << index);
这行代码执行以下操作:
(1 << index)
创建一个只有第index位为1的掩码gtArm.KeyState |= (1 << index)
使用按位或操作,将gtArm.KeyState的第index位设置为1,其他位保持不变例如,当index=4时(护理按键),操作过程为:
(1 << 4)
= 0x10 (二进制: 00010000)gtArm.KeyState |= 0x10
将gtArm.KeyState的第4位设置为1,其他位保持不变为了防止按键抖动,系统使用了计数器:
按键按下防抖:
使用trascnt1[index]
计数器,只有当连续多次(SCAN_ACK_COUNT
次)检测到按键按下时,才认为按键真正按下,并触发相应的事件。
按键释放防抖:
使用trascnt2[index]
计数器,只有当连续多次(SCAN_ACK_COUNT
次)检测到按键释放时,才认为按键真正释放,并触发相应的事件。
这种防抖机制确保了按键信号的稳定性,避免了由于机械抖动导致的误触发。
当按键状态变化且通过防抖处理后,系统会触发相应的事件:
按键按下事件:
// 文件: source/user/mapp_key.c
OSQPost(AmiMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_PRESSED));
按键释放事件:
// 文件: source/user/mapp_key.c
OSQPost(AmiMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_RELEASED));
这些事件会被发送到消息队列AmiMsgQueue
中,后续由事件处理系统进行处理。
按键事件通过消息队列AmiMsgQueue
传递给消息处理函数。在AmiTxTask
函数中会调用相应的处理函数:
// 文件: source/user/mapp_arm.c
void AmiTxTask(void)
{
T_MESSAGE *pmsg;
pmsg = OSQGet(AmiMsgQueue);
if (pmsg->id < NumOfAMIMsg) {
arryAmiTxFuncTbl[pmsg->id](pmsg->prm);
}
}
按键消息经过处理后,通过s_frmAmiTxKey
函数将按键代码转换为UART数据:
// 文件: source/user/mapp_arm.c
static void s_frmAmiTxKey(U16 prm)
{
U08 *buf = (U08*)NULL;
buf = &s_u8AmiTxBuf[0];
*buf++ = '$';
// 添加地址信息...
*buf++ = 'K';
*buf++ = 'E';
*buf++ = 'Y';
// 根据不同按键设置对应代码
if (HI_BYTE(prm) == 8) {
*buf++ = 0x30 + 0x0b;
} else if (HI_BYTE(prm) == 9) {
*buf++ = 0x30 + 0x0c;
} else if (HI_BYTE(prm) == 0xa) {
*buf++ = 0x30 + 0x0d;
} else if (HI_BYTE(prm) == 0xb) {
*buf++ = 0x30 + 0x0e;
} else {
*buf++ = 0x30 + HI_BYTE(prm);
}
*buf++ = ',';
gtArm.startworkflag = 1;
*buf++ = 0X30 + LO_BYTE(prm);
*buf++ = '#';
Uart2PutChar(s_u8AmiTxBuf, 9);
}
按键信号从物理按键被按下到最终通过UART发送出去,需要经过多个阶段的处理和转换。以下是这个完整流程的详细解释:
物理按键状态读取:
在mapp_key.c
的KeyTask
函数中,系统通过GPIO读取引脚状态:
// 文件: source/user/mapp_key.c
u8KeyVal[0] = !KEY_PB5_CALL_DET; // 呼叫按键
u8KeyVal[1] = !gtArm.PA4_Value; // 手柄检测
u8KeyVal[2] = KEY_PA7_EMEGENCY_DET; // 紧急按钮
u8KeyVal[3] = !KEY_PA15_REINFORCE_DET; // 增援按键
u8KeyVal[4] = !KEY_PB3_NURSE_DET; // 护理按键
u8KeyVal[5] = !KEY_PA5_CHANGE_DET; // 换药按键
u8KeyVal[6] = !KEY_PB4_CANCEL_DET; // 取消按键
按键状态存储:
读取的状态经过防抖处理后,存储在gtArm.KeyState
变量中:
// 文件: source/user/mapp_key.c
if (u8KeyVal[index] == 0) {
gtArm.KeyState &= ~(1 << index); // 清除对应位
} else {
gtArm.KeyState |= (1 << index); // 设置对应位
}
状态变化检测: 当按键状态发生变化且通过防抖处理后,系统会生成按键事件:
// 文件: source/user/mapp_key.c
// 按键按下事件
OSQPost(AmiMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_PRESSED));
消息队列结构: OSQPost函数投递消息到消息队列中,消息包含三个部分:
AmiMsgQueue
ARM_MSG_KEY
(表示这是按键消息)TO_WORD(index+5, KEY_PRESSED)
(高字节是按键索引+5,低字节是KEY_PRESSED表示按下状态) // TO_WORD宏定义(通常在头文件中)
#define TO_WORD(h, l) (((U16)(h) << 8) | (l))
消息获取:
在mapp_arm.c
的AmiTxTask
函数中,系统从消息队列中获取消息:
// 文件: source/user/mapp_arm.c
T_MESSAGE *pmsg;
pmsg = OSQGet(AmiMsgQueue);
处理函数映射: 根据消息ID调用对应的处理函数:
// 文件: source/user/mapp_arm.c
if (pmsg->id < NumOfAMIMsg) {
arryAmiTxFuncTbl[pmsg->id](pmsg->prm);
}
其中arryAmiTxFuncTbl
是一个函数指针数组,定义了不同消息ID对应的处理函数:
// 文件: source/user/mapp_arm.c
static void (*const arryAmiTxFuncTbl[NumOfAMIMsg])(U16 prm) = {
s_frmAmiTxEvent, // ARM_MSG_EVENT
s_frmAmiTxKey, // ARM_MSG_KEY
s_frmAmiTxReply, // ARM_MSG_REPLY
// 其他处理函数...
};
当接收到ARM_MSG_KEY类型的消息时,系统会调用s_frmAmiTxKey
函数。
s_frmAmiTxKey
函数将按键代码转换为UART协议格式的数据:
// 文件: source/user/mapp_arm.c
static void s_frmAmiTxKey(U16 prm)
{
U08 *buf = (U08*)NULL;
buf = &s_u8AmiTxBuf[0];
// 1. 帧头
*buf++ = '$';
// 2. 添加地址信息(从配置读取)
*buf++ = s_u8RoomId[0];
*buf++ = s_u8RoomId[1];
// 3. 添加命令类型 - "KEY"表示按键命令
*buf++ = 'K';
*buf++ = 'E';
*buf++ = 'Y';
// 4. 按键代码转换
// 根据按键索引设置对应代码
if (HI_BYTE(prm) == 8) {
*buf++ = 0x30 + 0x0b; // ASCII码转换
} else if (HI_BYTE(prm) == 9) {
*buf++ = 0x30 + 0x0c;
} else if (HI_BYTE(prm) == 0xa) {
*buf++ = 0x30 + 0x0d;
} else if (HI_BYTE(prm) == 0xb) {
*buf++ = 0x30 + 0x0e;
} else {
*buf++ = 0x30 + HI_BYTE(prm); // 其他按键,直接加0x30转ASCII
}
// 5. 分隔符
*buf++ = ',';
// 6. 按键状态(按下/释放)
gtArm.startworkflag = 1; // 标记系统开始工作
*buf++ = 0X30 + LO_BYTE(prm); // 状态码转ASCII:0=释放,1=按下
// 7. 帧尾
*buf++ = '#';
// 8. 发送数据到UART
Uart2PutChar(s_u8AmiTxBuf, 9); // 发送9字节数据
}
Uart2PutChar
函数将按键消息按照特定协议格式发送到UART接口:
// 文件: source/user/mapp_arm.c 或 source/user/uart.c
void Uart2PutChar(U08 *pu8data, U16 len)
{
U16 i;
for(i=0; i<len; i++) {
// 等待UART发送缓冲区空
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
// 发送一个字节
USART_SendData(USART2, *pu8data++);
}
}
最终发送的UART数据遵循以下格式:
$RRKEY<k>,<s>#
其中:
$
- 帧起始标记RR
- 房间/设备地址(两个字节)KEY
- 命令类型(表示按键消息)<k>
- 按键代码(ASCII码表示的十六进制数),
- 分隔符<s>
- 按键状态(1=按下,0=释放)#
- 帧结束标记例如,当护理按键(KEY4)被按下时,可能发送的数据为:
$01KEY9,1#
这表示设备地址01的护理按键被按下。
按键消息从物理按下到UART发送的完整流程为:
gtArm.KeyState
变量这个流程确保了按键信号能够被可靠地处理并转换为标准化的通信协议,供其他设备(如主控系统)接收和处理。
在代码片段中,我们看到pmsg->id
用于确定调用哪个处理函数:
// 文件: source/user/mapp_arm.c
pmsg = OSQGet(ArmMsgQueue); // 注意这里是ArmMsgQueue而非之前提到的AmiMsgQueue
if (pmsg->id < NumOfArmMsg) {
arryArmTxFuncTbl[pmsg->id](pmsg->prm); // 使用消息ID索引处理函数
}
那么这个pmsg->id
是如何设置的呢?这涉及到消息队列的核心机制:
消息结构定义: 系统定义了一个消息结构体,通常如下:
// 文件: source/user/mapp_os.h 或其他头文件
typedef struct {
U08 id; // 消息ID
U16 prm; // 消息参数
} T_MESSAGE;
消息队列的创建: 系统在初始化阶段创建消息队列:
// 文件: source/user/mapp_arm.c 或初始化文件
ArmMsgQueue = OSQCreate(sizeof(arryMsg) / sizeof(T_MESSAGE));
消息ID的分配: 系统通常用枚举定义各类消息ID:
// 文件: source/user/mapp_arm.h 或其他头文件
enum {
ARM_MSG_EVENT = 0, // 事件消息
ARM_MSG_KEY, // 按键消息
ARM_MSG_REPLY, // 回复消息
// 其他消息类型...
NumOfArmMsg // 消息类型总数
};
消息投递时ID的设置:
在OSQPost
函数中,第二个参数就是消息ID:
// 文件: source/user/mapp_key.c
OSQPost(ArmMsgQueue, ARM_MSG_KEY, TO_WORD(index+5, KEY_PRESSED));
// 消息队列 ^消息ID 消息参数
OSQPost函数实现: 这个函数将消息ID和参数封装到消息结构体中:
// 文件: source/user/mapp_os.c 或RTOS源码
U08 OSQPost(U08 qid, U08 id, U16 prm)
{
if (qid >= MAX_QUEUE || s_QueueSize[qid] >= MAX_QUEUE_SIZE) {
return FALSE;
}
// 设置消息ID和参数
s_arryMsg[qid][s_QueueSize[qid]].id = id; // 这里设置了pmsg->id
s_arryMsg[qid][s_QueueSize[qid]].prm = prm;
s_QueueSize[qid]++;
return TRUE;
}
OSQGet函数实现: 当任务调用OSQGet获取消息时,会返回包含id和prm的消息结构体:
// 文件: source/user/mapp_os.c 或RTOS源码
T_MESSAGE* OSQGet(U08 qid)
{
static T_MESSAGE msg;
if (qid >= MAX_QUEUE || s_QueueSize[qid] == 0) {
return NULL;
}
// 获取队列第一个消息
msg = s_arryMsg[qid][0];
// 队列前移
s_QueueSize[qid]--;
for (U08 i = 0; i < s_QueueSize[qid]; i++) {
s_arryMsg[qid][i] = s_arryMsg[qid][i+1];
}
return &msg; // 返回消息结构体,其中包含id和prm
}
因此,pmsg->id
是在消息投递(OSQPost)时设置的,用于标识消息类型,接收方根据这个ID选择相应的处理函数。在按键处理流程中,当按键状态变化并通过防抖处理后,系统会投递一个ID为ARM_MSG_KEY的消息,然后在消息处理任务中根据这个ID调用按键处理函数s_frmArmTxKey。
在mapp_bus.c
文件的GPIO_KEY_Tick
函数中,对gtArm.KeyState
进行再次处理,将按键状态转换为功能状态:
// 文件: source/user/mapp_bus.c
void GPIO_KEY_Tick(void)
{
static unsigned char KeyoldState = 0;
// 复位按键状态
if (KEY0 == 2) KEY0 = 0;
if (KEY1 == 2) KEY1 = 0;
// ...
// 检测按键状态变化
if ((KeyoldState & 0x01) != (gtArm.KeyState & 0x01)) {
if (gtArm.KeyState & 0x01) {
KEY1 = 1;
} else {
KEY1 = 2;
}
}
// 其他按键检测...
// 特殊组合按键检测
if ((KEY0 == 1) && (KEY2 == 1)) {
KEY0 = 0;
KEY2 = 0;
KEY6 = 1;
key6laycnt = 200; // 拔针释放两个按键不同步,需要延时
} else {
KEY6 = 0;
if (key6laycnt) {
key6laycnt--;
KEY0 = 0;
KEY2 = 0;
}
}
KeyoldState = gtArm.KeyState;
}
这里存在一个潜在问题:按键状态处理可能存在冲突:
// 文件: source/user/mapp_bus.c
// 第一次检测 - 位掩码0x04
if ((KeyoldState & 0x04) != (gtArm.KeyState & 0x04)) { ... }
// 第二次检测 - 位掩码0x40
if ((KeyoldState & 0x40) != (gtArm.KeyState & 0x40)) { ... }
这可能导致按键状态混乱,特别是当0x40位发生变化时。
按键状态转换后,在BUSTask
函数中根据按键状态执行相应的功能逻辑:
// 文件: source/user/mapp_bus.c
void BUSTask(void)
{
// 处理护理按键状态计时
if (nursestate) { // 护理按键延时用
ii++;
} else {
ii = 0;
}
// 语音命令处理
sentvoicecmd(VoiceCmdApp);
// 按键功能处理
if (KEY_OLD[2] != KEY2) { // 换药
if (KEY2 == 1) {
brm.DaddressDate = (uint16_t)((Addrp[0] << 8) | Addrp[1]);
brm.DataType = 0x4;
BUS_SendData(brm);
VoiceCmdApp = CMD_CHANGE;
}
KEY_OLD[2] = KEY2;
} else if (KEY_OLD[3] != KEY3) { // 取消 - 原增援按键改为取消
if (KEY3 == 1) {
brm.DaddressDate = (uint16_t)((Addrp[0] << 8) | Addrp[1]);
brm.DataType = 0x05;
BUS_SendData(brm);
VoiceCmdApp = CMD_CALLESC;
}
KEY_OLD[3] = KEY3;
} else if (KEY_OLD[4] != KEY4) { // 护理 - 原护理按键位置不变
// 5秒后再按,要发退出
if (ii > 500) { // 5秒
nursestate = 0;
if (KEY4 == 1) {
brm.DaddressDate = (uint16_t)((Addrp[0] << 8) | Addrp[1]);
brm.DataType = 0x03;
BUS_SendData(brm);
VoiceCmdApp = CMD_NURSEOUT;
}
} else {
ii = 0;
nursestate = 1;
if (KEY4 == 1) {
brm.DaddressDate = (uint16_t)((Addrp[0] << 8) | Addrp[1]);
brm.DataType = 0x03;
BUS_SendData(brm);
VoiceCmdApp = CMD_NURSEIN;
}
}
KEY_OLD[4] = KEY4;
} else if (KEY_OLD[5] != KEY5) { // 增援 - 原取消按键改为增援
if (KEY5 == 1) {
brm.DaddressDate = (uint16_t)((Addrp[0] << 8) | Addrp[1]);
brm.DataType = 0x02;
BUS_SendData(brm);
VoiceCmdApp = CMD_REINFORCE;
}
KEY_OLD[5] = KEY5;
}
// 其他按键处理...
}
语音命令由sentvoicecmd
函数处理:
// 文件: source/user/mapp_bus.c
void sentvoicecmd(unsigned char prm)
{
static unsigned char bcnt = 0;
static unsigned char VoiceCmdOld = 0;
static unsigned short s5muitecnt = 0; // 5秒后可以继续发语音
if (BUS_SendInex) { // 总线数据先发
return;
}
if (prm == CMD_NULL) {
VoiceCmdOld = prm;
s5muitecnt = 0;
VOICE_PF7_ON; // 恢复呼叫通话
return;
}
s5muitecnt++;
if (s5muitecnt > 400) { // 5秒后关闭
VOICE_PF7_ON; // 恢复呼叫通话
VoiceCmdApp = CMD_NULL;
VoiceCmdOld = CMD_NULL;
bcnt = 10;
return;
}
if (VoiceCmdOld != prm) {
// 语音芯片播报声音通道打开
VOICE_PF7_OFF;
VoiceCmdAppAck = VoiceCmdApp;
s5muitecnt = 0;
VoiceCmd = 0xfe;
VoiceCmdFlag = 1;
bcnt = 10;
VoiceCmdOld = prm;
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
}
// 发送语音指令
if (bcnt == 0) {
VoiceCmdFlag = 1;
VoiceCmd = VoiceCmdOld;
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
bcnt = 100;
} else if (VoiceCmd == 0xfe) {
bcnt--;
}
}
语音芯片通过PWM波形控制,在TIM6定时器中断中实现:
// 文件: source/user/mapp_bus.c
void TIM6_WTV3808S_TASK(void)
{
static unsigned char voiceflag = 0;
switch (voiceflag) {
case 0:
if (VoiceCmdFlag) {
voiceflag = 1;
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
}
break;
case 1:
VOICE_PB15_OFF;
VoiceCmdFlag++;
if (VoiceCmdFlag > 55) { // 5ms
voiceflag++;
VoiceCmdFlag = 150;
}
break;
case 2:
VOICE_PB15_ON;
// 根据VoiceCmd的位生成PWM波形
// ...
break;
// 更多状态...
}
}
BUSTask
函数是整个按键处理系统的最上层逻辑实现,每10ms被调用一次,负责处理按键功能和语音命令:
// 文件: source/user/mapp_bus.c
void BUSTask(void)
{
// 1. 语音命令处理
sentvoicecmd(VoiceCmdApp);
// 2. 护理按键状态计时
if (nursestate) { ii++; } else { ii = 0; }
// 3. 调用底层功能处理函数
GPIO_MIC_SW(Micswitch);
GPIO_LED_Tick();
BUS_Rev_Tick();
GPIO_KEY_Tick(); // 处理按键状态
// 4. 语音通道控制
// 根据Voiceswitch和VoiceCmdApp控制VOICESW_H/L
// 5. 按键功能处理逻辑
if (KEY_OLD[2] != KEY2) { // 换药
if (KEY2 == 1) {
// 发送数据包
VoiceCmdApp = CMD_CHANGE;
}
KEY_OLD[2] = KEY2;
}
else if (KEY_OLD[3] != KEY3) { // 取消
if (KEY3 == 1) {
// 发送数据包
VoiceCmdApp = CMD_CALLESC;
}
KEY_OLD[3] = KEY3;
}
else if (KEY_OLD[4] != KEY4) { // 护理
if (ii > 500) { // 5秒后再按 = 退出
// 发送退出命令
VoiceCmdApp = CMD_NURSEOUT;
} else { // 进入
// 发送进入命令
VoiceCmdApp = CMD_NURSEIN;
}
KEY_OLD[4] = KEY4;
}
// 类似处理其他按键...
}
sentvoicecmd
函数处理语音命令GPIO_KEY_Tick
更新按键状态函数采用"状态变化检测"模式处理按键:
if (KEY_OLD[#] != KEY#) { // 检测按键状态变化
if (KEY# == 1) { // 按键按下时执行
// 构造数据包并发送
VoiceCmdApp = CMD_XXX; // 设置语音命令
}
KEY_OLD[#] = KEY#; // 更新旧状态
}
护理按键:通过nursestate
和计数器ii
实现长按检测
语音通道控制:根据状态控制语音通道开关
按键 | 数据类型 | 语音命令 | 功能 |
---|---|---|---|
KEY2 | PA5 | 换药 | CMD_CHANGE |
KEY3 | PB4 | 取消 | CMD_CALLESC |
KEY4 | PB3 | 护理 | CMD_NURSEIN(按下) / CMD_NURSEOUT(5秒后再按) |
KEY5 | PA15 | 增援 | CMD_REINFORCE |
KEY6 | 组合 | 拔针 | CMD_NEEDLES |
BUSTask
是整个按键系统的业务核心,将底层按键状态转换为实际功能操作和语音命令,完成了从物理信号到用户功能的最终映射。
各按键触发的语音命令总结如下:
按键 | 引脚 | 功能 | 语音命令 |
---|---|---|---|
KEY2 | PA5 | 换药 | CMD_CHANGE |
KEY3 | PB4 | 取消 | CMD_CALLESC |
KEY4 | PB3 | 护理 | CMD_NURSEIN(按下) / CMD_NURSEOUT(5秒后再按) |
KEY5 | PA15 | 增援 | CMD_REINFORCE |
KEY6 | 组合 | 拔针 | CMD_NEEDLES |
按键触发可能存在逻辑冲突问题:
// 文件: source/user/mapp_bus.c
if ((KeyoldState & 0x04) != (gtArm.KeyState & 0x04)) { ... }
if ((KeyoldState & 0x40) != (gtArm.KeyState & 0x40)) { ... }
这可能导致KEY3被错误地更新,特别是当两个位同时或先后变化时。
护理按键(KEY4)按下后会设置nursestate=1并开始累加计数器ii。如果此时由于0x40位变化错误触发了KEY3,且ii>500,系统可能会误认为这是护理功能的长按退出,从而触发CMD_NURSEOUT语音。
当组合按键KEY0和KEY2同时按下触发拔针功能时,会将两个按键状态清零,这可能与其他状态检测逻辑产生冲突。
本文档详细分析了按键引脚从配置到语音处理的完整工作流程,包括按键信号采集、按键去抖、消息处理、功能逻辑处理和语音命令处理等环节。通过对代码的分析,发现了可能导致按键功能混淆的潜在问题,主要是由于按键状态检测逻辑中存在重复检测和状态混淆的情况。