jim_lilei 发表于 5 天前

STC15H2K64S4单片机P1.0-时钟 P1.1数据模拟PS2给PC发送A键

使用单片机STC15H2K64S4单片机的P1.0作为时钟,P1.1作为数据线,地线 +5电源 四根线与电脑端的键盘PS/2接口相连,想用单片机模拟键盘A键被按下时发送通码0x1c给PC机,但是在PC机上一直监测不到A字符,代码如下




#include <STC15H2K64S4.H>
// 引脚定义
#define KEY_ROW_PORT P2          // 键盘行端口(P2.0~P2.5)
#define KEY_COL_PORT P0          // 键盘列端口(P0.0~P0.6)
sbit PS2_CLK = P1^0 ;       // PS/2 时钟引脚(P1.0)
sbit PS2_DATA =P1^1;      // PS/2 数据引脚(P1.1)
#define uchar unsigned char
uchar KeyState = {0};// 按键状态缓存(0:未按下,1:按下)
uchar LastKey = 0xFF;      // 上一次按键键码(初始化为无效值)
bit KeyPressedFlag = 0;      // 按键按下标志位


// 延时函数(微秒级,适配11.0592MHz晶振)
void DelayUs(uchar us)
{
    while(us--)
   {
      _nop_();
      _nop_();
      _nop_();
    }
}

// 延时函数(毫秒级,适配11.0592MHz晶振)
void DelayMs(unsigned int ms)
{
    uchar i, j;
    while(ms--)
   {
      i = 11;
      j = 190;
      do
        {
            while(--j);
      }
        while(--i);
    }
}

// PS/2 发送一个字节(含起始位、8位数据、1位奇偶位、1位停止位)
void PS2_SendByte(uchar dat)
{
    uchar i;
    bit parity = 0;// 奇偶位(PS/2协议:数据位1的个数为奇数时,奇偶位为1,总1数为偶数)
   
    // 1. 拉低CLK和DATA,准备发送起始位(主机主动拉低CLK至少10us)
    PS2_CLK = 0;
    DelayUs(15);
    PS2_DATA = 0;// 起始位(低电平)
    DelayUs(5);
    PS2_CLK = 1;   // 释放CLK,等待从机(计算机)拉低CLK
   
    // 2. 发送8位数据(LSB先传,即从bit0开始)
    for(i = 0; i < 8; i++)
   {
      while(PS2_CLK);// 等待从机拉低CLK(从机准备好接收)
      PS2_DATA = (dat >> i) & 0x01;// 发送当前数据位
      parity ^= PS2_DATA;            // 计算奇偶位(异或累加1的个数)
      while(!PS2_CLK);               // 等待从机释放CLK(完成当前位接收)
    }
   
    // 3. 发送奇偶位
    while(PS2_CLK);
    PS2_DATA = parity;
    while(!PS2_CLK);
   
    // 4. 发送停止位(高电平)
    while(PS2_CLK);
    PS2_DATA = 1;
    while(!PS2_CLK);
    PS2_CLK = 1;// 释放CLK,完成1字节发送
    DelayUs(10);
}



// 主函数(初始化+循环扫描+PS2发送)
void main()
{
    // 端口初始化:行端口(推挽输出),列端口(上拉输入),PS2引脚(开漏输出,兼容PS2协议)
    P2M0 = 0x3F;// P2.0~P2.5 推挽输出(0x3F = 00111111)
    P2M1 = 0x00;
    P0M0 = 0x00;// P0.0~P0.6 上拉输入(开漏输入模式)
    P0M1 = 0x7F;// 0x7F = 01111111
    P1M0 = 0x03;// P1.0~P1.1 开漏输出(PS2需要开漏,匹配总线电平)
    P1M1 = 0x00;
   
    PS2_CLK = 1;// 初始释放PS2 CLK和DATA(高电平)
    PS2_DATA = 1;
    DelayMs(100); // 系统稳定延时
   
    while(1)
   {
          PS2_SendByte(0x1c);    // 向计算机发送PS2键码A键
          PS2_SendByte(0xF0);       // 发送键码释放帧(PS2协议:释放键码=0xF0+按下键码)
          PS2_SendByte(0x1c);
          DelayMs(1000);// 降低扫描频率,减少CPU占用
    }
}


国芯人工智能 发表于 5 天前

你提出的这个问题涉及使用 STC15H2K64S4 单片机通过 PS/2 接口协议 模拟键盘按键(A键)发送通码 0x1C 给 PC。你已经实现了基本的硬件连接(P10 为时钟,P11 为数据线)和部分代码结构,但在 PC 端无法接收到预期的字符“A”。

以下是对你的问题的全面分析和优化建议:

一、PS/2 协议基础

PS/2 是一种同步串行通信协议,使用两根线:
CLK(时钟线):由主机(PC)或设备(键盘)控制。
DATA(数据线):数据在时钟的下降沿被采样。

1. 数据帧格式(设备发送给主机)

标准 PS/2 数据帧如下(11 位):

| 位序 | 内容             |
|------|------------------|
| 0    | 起始位(0)       |
| 1~8| 数据位(LSB 先发)|
| 9    | 奇偶校验位      |
| 10   | 停止位(1)       |

2. 发送时序要点
数据线在时钟下降沿时必须稳定。
时钟频率通常在 10~20kHz,即每个位周期约 50~100 μs。
设备必须控制时钟线(拉低 -> 释放)以发送数据。

二、代码分析与问题定位

你当前的代码片段中,仅给出了延时函数、引脚定义及全局变量,但未提供完整的 PS/2 发送函数。根据你描述的“发送 A 键通码 0x1C 但 PC 无反应”,可能的问题如下:

1. 时序控制不准确

PS/2 的时序要求非常严格,尤其是时钟的控制和数据的稳定时间。若延时不足或过长,会导致主机(PC)无法正确识别数据。

2. 数据位顺序错误(LSB vs MSB)

PS/2 协议中数据位是 LSB 先发。若代码中直接发送 0x1C(即二进制 00011100),则实际发送顺序应为:

起始位(0) + 00011100 -> LSB 先发:00011100 实际发送顺序为 00011100(不变)

但注意,如果你在代码中没有正确地逐位发送,或在位移操作时出错,可能导致数据发送错误。

3. 校验位错误

PS/2 要求使用 奇校验,即整个数据帧中 1 的个数必须为奇数。若你未正确计算奇校验位,PC 会丢弃该帧。

4. 时钟线控制错误

PS/2 设备在发送数据时必须主动控制时钟线。如果你的代码中 CLK 线始终由 PC 控制,或你未正确地在发送期间拉低 CLK,会导致 PC 无法接收数据。

三、建议解决方案

1. 完整的 PS/2 发送函数示例(基于你当前硬件)

c
void SendPS2Byte(uchar data)
{
    uchar i;
    uchar parity = 1; // 奇校验初始化为1,确保最终1的个数为奇数

    // 拉低数据线前先拉低时钟线,确保主机不主动控制
    PS2CLK = 0;
    DelayUs(10);

    // 起始位
    PS2DATA = 0;
    DelayUs(10);
    PS2CLK = 1;
    DelayUs(10);
    PS2CLK = 0;

    // 发送8位数据(LSB先发)
    for(i = 0; i < 8; i++)
    {
      if(data & 0x01)
      {
            PS2DATA = 1;
            parity ^= 1; // 计算奇校验
      }
      else
      {
            PS2DATA = 0;
      }
      data >>= 1;

      DelayUs(10);
      PS2CLK = 1;
      DelayUs(10);
      PS2CLK = 0;
    }

    // 发送奇校验位
    PS2DATA = parity;
    DelayUs(10);
    PS2CLK = 1;
    DelayUs(10);
    PS2CLK = 0;

    // 停止位
    PS2DATA = 1;
    DelayUs(10);
    PS2CLK = 1;
    DelayUs(10);
    PS2CLK = 0;

    // 释放数据线与时钟线
    PS2DATA = 1;
    PS2CLK = 1;
}

2. 使用函数发送 A 键通码(0x1C)

c
void SendAKey()
{
    SendPS2Byte(0x1C); // A键通码
}

3. 注意事项
时钟线必须由设备(单片机)控制,在发送期间主动拉低并释放。
数据线在时钟下降沿时必须稳定,建议在拉低 CLK 后延时再改变 DATA。
奇校验位必须正确计算,否则 PC 会丢弃数据帧。
延时函数需适配当前主频(110592MHz),建议使用定时器或更精确的延时方式。

四、硬件连接建议

确保你的硬件连接如下:

| 单片机引脚 | 功能   | 连接至 PS/2 接口 |
|------------|----------|------------------|
| P1^0       | PS2CLK| CLK 引脚         |
| P1^1       | PS2DATA | DATA 引脚      |
| GND      | 地       | GND            |
| VCC      | 电源   | +5V            |

注意:PS/2 接口通常是 6 针 mini-DIN 接口,其中仅使用 CLK、DATA、GND、VCC 四根线。

五、调试建议

1. 使用示波器或逻辑分析仪 观察 CLK 和 DATA 波形,确认是否符合 PS/2 协议格式。
2. 尝试发送多个帧,如发送 0x1C 多次,观察是否能触发一次有效接收。
3. 尝试发送其他键码,如 0x1B(Z键)等,确认是否为码值错误。
4. 尝试使用 USB PS/2 转换器 测试,部分主板或笔记本可能不支持纯 PS/2 接口。

六、总结

你当前的问题主要集中在 PS/2 协议实现不完整 上,包括:
时钟线控制不正确
数据位顺序处理错误
校验位未正确计算
延时控制不精确

建议你根据上述完整函数进行修改,并配合逻辑分析仪进行调试。只要时序和数据格式正确,PC 端应能正确接收到“A”字符。

如需进一步帮助,欢迎继续交流。

[本文内容由国芯人工智能辅助生成,仅供参考]

神农鼎 发表于 5 天前

STC8H8K64U系列的USB支持 PS2



jim_lilei 发表于 3 天前

我看你的说明时钟频率10-20Khz,位周期50-100us,在程序里你用DelayUs(10);延时10us是什么意思?

ercircle 发表于 3 天前

示波器\逻辑分析仪 抓下波形和正常的对比下

jim_lilei 发表于 昨天 14:00

Ps2接口不是即插即用的,用单片机模拟时,只是一直的给pc发送数据,我按照师傅给的建议试了,pc收不到数据
代码如下:
#include "reg51.h"
#include "intrins.h"

typedef unsigned char BYTE;
typedef unsigned int WORD;



sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
sfr P1M1 = 0x91;
sfr P1M0 = 0x92;
sfr P2M1 = 0x95;
sfr P2M0 = 0x96;
sfr P3M1 = 0xb1;
sfr P3M0 = 0xb2;
sfr P4M1 = 0xb3;
sfr P4M0 = 0xb4;
#define delaym(); _nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_();      
#define Delay2(); _nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();   
#define delays(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_();
#define delayms(); _nop_();_nop_();_nop_();

sfr AUXR= 0x8e;               //辅助寄存器

sfr P_SW1   = 0xA2;             //外设功能切换寄存器1

#define S1_S0 0x40            //P_SW1.6
#define S1_S1 0x80            //P_SW1.7

sbit P34 = P3^4;
sbit PS2_CLK=P1^0;
sbit PS2_DATA=P1^1;
bit busy;

void SendData(BYTE dat);
void SendString(char *s);
void delay()
{
    int i, j;

    for (i=0; i<1000; i++)
    for (j=0; j<500; j++);
}
/*延时微秒*/
void DelayUs(unsigned int us)
{
Delay2();
Delay2();
Delay2();
Delay2();
}
/*延时毫秒*/
void DelayMs(unsigned int ms)
{
unsigned char i,j;
while(ms--)
{
   i=16;
   j=190;
   do
   {
      while(--j);
   }
   while(--i);
}
}

/*PS2发送函数    */
void PS2_SendByte(unsigned char dat)
{
unsigned char i=0;
bit parity=1;
PS2_CLK=0;
DelayUs(50);
//起始位
PS2_DATA=0;
DelayUs(50);
PS2_CLK=1;
DelayUs(50);
PS2_CLK=0;

//发送8位数据 LSB在前
for(i=0;i<8;i++)
{
if(dat&0x01)
{
   PS2_DATA=1;
   parity^=1;
}
else
{
   PS2_DATA=0;
}
dat>>=1;
DelayUs(50);
PS2_CLK=1;
DelayUs(50);
PS2_CLK=0;
}
//发送奇偶校验位
PS2_DATA=parity;
DelayUs(50);
PS2_CLK=1;
DelayUs(50);
PS2_CLK=0;
//停止位
PS2_DATA=1;
DelayUs(50);
PS2_CLK=1;
DelayUs(50);
PS2_CLK=0;

//释放时钟和数据线
PS2_CLK=1;
PS2_DATA=1;
}
void main()
{
    P1M1 = 0x00;
    P1M0 = 0x00;

   P3M1 = 0x00;
    P3M0 = 0x00;
    SCON = 0x50;                //8位可变波特率
    AUXR = 0x00;                //定时器1为12T模式
    TMOD = 0x20;                //定时器1为模式2(8位自动重载)
    TH1=0xfd;
    TL1=0xfd;
    TR1 = 1;                  //定时器1开始工作
    ES = 1;                     //使能串口中断
    EA = 1;
    while(1)
    {
       PS2_SendByte(0x1c);
       DelayMs(500);    //500毫秒
   }
}C:\Users\DELL\Desktop\微信图片_20251015135711_134_24.jpeg
页: [1]
查看完整版本: STC15H2K64S4单片机P1.0-时钟 P1.1数据模拟PS2给PC发送A键