《圈圈教你玩USB》 第二章 USB 硬件系统设计_测试程序的编写和调试——看书笔记

2.11 测试程序的编写和调试

2.11.4 按键驱动的编写

考虑到以后程序的方便性,这里使用定时器中断方式来扫描按键。定时器每隔5ms中断一次,即每5ms
扫描一次键盘。

key.c

    1)先初始化好一个定时器,此处选择定时器0作为按键扫描的定时器,而定时器1留作串口波特率发生器。
初始化定时器就是设置定时器的工作模式,启动定时器等。
   
   
  1. //定时器0初始化,用来做按键扫描
  2. void InitTimer0(void)
  3. {
  4. TMOD&=0xF0; //定时器低4位是控制定时器0的,先置0
  5. TMOD|=0x01; //然后将最低位置1,最终选择16位定时器模式
  6. ET0=1; //允许定时器0中断
  7. TR0=1; //启动定时器0
  8. }
    2)再初始化键盘
    
    
  1. volatile uint8 idata KeyCurrent,KeyOld,KeyNoChangedTime;
  2. volatile uint8 idata KeyPress;
  3. volatile uint8 idata KeyDown,KeyUp,KeyLast;
  4. volatile uint8 KeyCanChange;
  5. //函数功能:键盘初始化
  6. void InitKeyboard(void)
  7. {
  8. KeyIO=0xFF; //键盘对应的口设置为输入状态
  9. KeyPress=0; //无按键按下
  10. KeyNoChangedTime=0;
  11. KeyOld=0;
  12. KeyCurrent=0;
  13. KeyLast=0;
  14. KeyDown=0;
  15. KeyUp=0;
  16. InitTimer0(); //初始化定时器
  17. KeyCanChange=1; //允许键值改变
  18. }
    KeyCurrent、KeyOld、KeyLast和KeyNoChangedTime是扫描按键使用的变量,应用程序不直接使用它们。
    KeyPress、KeyDown、KeyUp、KeyCanChange是提供给应用程序使用的变量。
    KeyPress表示当前被按住不放的键;
    KeyDown表示新按下的键;
    KeyUp表示心松开的键;
    KeyCanChange是应用程序用来控制是否允许新的扫描。
    当某个按键被按下时,KeyPress对应位被置1,并且KeyDown对应位也置1;当按键松开后,KeyPress对应
位为0,KeyUp对应位被置1。
    3)定时器0中断处理
   
   
  1. //函数功能:定时器0中断处理。
  2. //22.1184M晶体约5ms中断一次。
  3. void Timer0Isr(void) interrupt 1
  4. {
  5. //定时器0重装,定时间隔为5ms,加15是为了修正重装所花费时间
  6. //这个值可以通过软件仿真来确定,在这里设置断点,调整使两次运行
  7. //时间差刚好为5ms即可。
  8. TH0=(65536-Fclk/1000/12*5+15)/256;
  9. TL0=(65536-Fclk/1000/12*5+15)%256;
  10. if(!KeyCanChange)return; //如果正在处理按键,则不再扫描键盘
  11. //开始键盘扫描
  12. //保存按键状态到当前按键情况
  13. //KeyCurrent总共有8个bit
  14. //当某个开关按下时,对应的bit为1
  15. KeyCurrent=GetKeyValue(); //读取键值,GetKeyValue()其实是个宏,不是函数,
  16. //这里故意写成函数的样子,美观。它的定义在
  17. //key.h文件中
  18. if(KeyCurrent!=KeyOld) //如果两次值不等,说明按键情况发生了改变
  19. {
  20. KeyNoChangedTime=0; //键盘按下时间为0
  21. KeyOld=KeyCurrent; //保存当前按键情况
  22. return; //返回
  23. }
  24. else
  25. {
  26. KeyNoChangedTime++; //按下时间累计
  27. if(KeyNoChangedTime>=1) //如果按下时间足够
  28. {
  29. KeyNoChangedTime=1;
  30. KeyPress=KeyOld; //保存按键
  31. KeyDown|=(~KeyLast)&(KeyPress); //求出新按下的键
  32. KeyUp|=KeyLast&(~KeyPress); //求出新释放的键
  33. KeyLast=KeyPress; //保存当前按键情况
  34. }
  35. }
  36. }

2.11.5 串口驱动的编写

1. 串口驱动的重要性:

    调试时,在对应位置打印出信息,可以知道程序走过哪些步骤;

2. usart.c

在使用串口之前,必须对串口进行初始化。
   
   
  1. //函数功能:串口初始化
  2. void InitUART(void)
  3. {
  4. EA=0;         //暂时关闭中断
  5. TMOD&=0x0F;  //定时器1模式控制在高4位
  6. TMOD|=0x20; //定时器1工作在模式2,自动重装模式
  7. SCON=0x50; //串口工作在模式1
  8. TH1=256-Fclk/(BitRate*12*16); //计算定时器重装值
  9. TL1=256-Fclk/(BitRate*12*16);
  10. PCON|=0x80; //串口波特率加倍
  11. ES=1; //串行中断允许
  12. TR1=1; //启动定时器1
  13. REN=1; //允许接收
  14. EA=1; //允许中断
  15. }
串口中断处理函数
   
   
  1. volatile uint8 Sending;
  2. //函数功能:串口中断处理。
  3. void UartISR(void) interrupt 4
  4. {
  5. if(RI) //收到数据
  6. {
  7. RI=0; //清中断请求,因为这里只发送,不接受,故此处只要清除中断标志即可
  8. }
  9. else //发送完一字节数据
  10. {
  11. TI=0;
  12. Sending=0; //清正在发送标志
  13. }
  14. }
发送单个字符函数
   
   
  1. //函数功能:往串口发送一字节数据。
  2. //入口参数:d: 要发送的字节数据。
  3. void UartPutChar(uint8 d)
  4. {
  5. SBUF=d; //将数据写入到串口缓冲
  6. Sending=1; //设置发送标志
  7. while(Sending); //等待发送完毕
  8. }
发送一个字符串函数
   
   
  1. //函数功能:发送一个字符串。
  2. //入口参数:pd:要发送的字符串指针。
  3. void Prints(uint8 * pd)
  4. {
  5. while((*pd)!='\0') //发送字符串,直到遇到0才结束
  6. {
  7. UartPutChar(*pd); //发送一个字符
  8. pd++;            //移动到下一个字符
  9. }
  10. }
以HEX格式发送一个整数
   
   
  1. code uint8 HexTable[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
  2. //函数功能:将短整数按十六进制发送。
  3. //入口参数:待发送的整数。
  4. void PrintShortIntHex(uint16 x)
  5. {
  6. uint8 i;
  7. uint8 display_buffer[7];
  8. display_buffer[6]=0;
  9. display_buffer[0]='0';
  10. display_buffer[1]='x';
  11. for(i=5;i>=2;i--) //将整数转换为4个字节的HEX值
  12. {
  13. display_buffer[i]=HexTable[(x&0xf)];
  14. x>>=4;
  15. }
  16. Prints(display_buffer);
  17. }

2.11.6 PDIUSBD12读写函数和读ID的实现

1. 读D12的ID号的目的

    知道D12是否正常工作

2. PDIUSBD12的读写时序图


分析:CS_N是片选信号。上图所示,只有片选信号拉低时,下面的操作才有意义。
          A0是地址线,用于选择是命令还是数据。A0为1时,表示操作的是命令;A0为0时,表示操作的是数据。
          WR_N是写信号,表示WR_N的上升沿将数据写入芯片中。数据必须在上升沿的前后稳定地保持一段时
   间(即图中的tWDSU和tWDH)才能可靠写入。
          RD_N是读选通信号,在读数据时,先将RD_N置低,等待tRLDD时间后,数据将出现在数据总线DATA
   [7:0]上,这时可以读取数据。读取完后,将RD_N拉高,数据在总线上的数据将在tRHDZ时间后消失。

3. PDIUSBD12.c

    根据上面的分析,可以得出:
   写命令的操作过程为:先将A0置高(即设置为命令状态),再讲WR_N置低,把需要发送的命令放到数据总线上,
再讲WR_N置高。这样将产生一个上升沿,从而把数据写入了D12中。写完后,须将总线设置为输入状态,以避免
总线冲突。
    写数据的操作过程为:只要将上面的A0设置为低即可(设置为数据状态)。
   
   
  1. //函数功能:D12写命令。
  2. //入口参数:Command:一字节命令。
  3. void D12WriteCommand(uint8 Command)
  4. {
  5. D12SetCommandAddr(); //设置为命令地址
  6. D12ClrWr();            //WR置低
  7. D12SetPortOut();       //将数据口设置为输出状态(注意这里为空宏,移植时可能有用)
  8. D12SetData(Command);  //输出命令到数据口上
  9. D12SetWr();            //WR置高
  10. D12SetPortIn();        //将数据口设置为输入状态,以备后面输入使用
  11. }
    读一个字节的操作过程为:先将A0置低(即设置为数据状态),再将RD_N置低(表示读数据),读取P0口上的数据
并保存,最后将RD_N置高,结束读过程。最后函数返回读取到的数据。
   
   
  1. //函数功能:读一字节D12数据。
  2. //返 回:读回的一字节。
  3. uint8 D12ReadByte(void)
  4. {
  5. uint8 temp;
  6. D12SetDataAddr(); //设置为数据地址
  7. D12ClrRd();        //RD置低
  8. temp=D12GetData(); //读回数据
  9. D12SetRd();        //RD置高
  10. return temp;       //返回读到数据
  11. }
    读取2字节的ID号
   
   
  1. //函数功能:读D12的ID。
  2. //返 回:D12的ID。
  3. uint16 D12ReadID(void)
  4. {
  5. uint16 id;
  6. D12WriteCommand(Read_ID);        //写读ID命令
  7. id=D12ReadByte();                //读回ID号低字节
  8. id|=((uint16)D12ReadByte())<<8;  //读回ID号高字节
  9. return id;
  10. }

4. 修改main函数

   在main.c中增加读取并显示ID号的代码,检验芯片是否焊接正确。

main.c

   
   
  1. /********************************************************************
  2. 函数功能:主函数。
  3. 入口参数:无。
  4. 返 回:无。
  5. 备 注:无。
  6. ********************************************************************/
  7. void main(void) //主函数
  8. {
  9. uint8 i;
  10. uint16 id;
  11. EA=1; //打开中断
  12. InitKeyboard(); //初始化按键
  13. InitUART(); //初始化串口
  14. for(i=0;i<16;i++) //显示信息
  15. {
  16. Prints(HeadTable[i]);
  17. }
  18. id=D12ReadID();
  19. Prints("Your D12 chip\'s ID is: ");
  20. PrintShortIntHex(id);
  21. if(id==0x1012)
  22. {
  23. Prints(". ID is correct! Congratulations!\r\n\r\n");
  24. }
  25. else
  26. {
  27. Prints(". ID is incorrect! What a pity!\r\n\r\n");
  28. }
  29. while(1) //死循环
  30. {
  31. LEDs=~KeyPress; //将按键结果取反后控制LED
  32. if(KeyDown) //有键按下
  33. { //处理按下的键
  34. if(KeyDown&KEY1)
  35. {
  36. Prints("KEY1 down\r\n");
  37. KeyDown&=~KEY1;
  38. }
  39. if(KeyDown&KEY2)
  40. {
  41. Prints("KEY2 down\r\n");
  42. KeyDown&=~KEY2;
  43. }
  44. if(KeyDown&KEY3)
  45. {
  46. Prints("KEY3 down\r\n");
  47. KeyDown&=~KEY3;
  48. }
  49. if(KeyDown&KEY4)
  50. {
  51. Prints("KEY4 down\r\n");
  52. KeyDown&=~KEY4;
  53. }
  54. if(KeyDown&KEY5)
  55. {
  56. Prints("KEY5 down\r\n");
  57. KeyDown&=~KEY5;
  58. }
  59. if(KeyDown&KEY6)
  60. {
  61. Prints("KEY6 down\r\n");
  62. KeyDown&=~KEY6;
  63. }
  64. if(KeyDown&KEY7)
  65. {
  66. Prints("KEY7 down\r\n");
  67. KeyDown&=~KEY7;
  68. }
  69. if(KeyDown&KEY8)
  70. {
  71. Prints("KEY8 down\r\n");
  72. KeyDown&=~KEY8;
  73. }
  74. }
  75. if(KeyUp)//有键释放
  76. {//处理释放的键
  77. if(KeyUp&KEY1)
  78. {
  79. Prints("KEY1 up\r\n");
  80. KeyUp&=~KEY1;
  81. }
  82. if(KeyUp&KEY2)
  83. {
  84. Prints("KEY2 up\r\n");
  85. KeyUp&=~KEY2;
  86. }
  87. if(KeyUp&KEY3)
  88. {
  89. Prints("KEY3 up\r\n");
  90. KeyUp&=~KEY3;
  91. }
  92. if(KeyUp&KEY4)
  93. {
  94. Prints("KEY4 up\r\n");
  95. KeyUp&=~KEY4;
  96. }
  97. if(KeyUp&KEY5)
  98. {
  99. Prints("KEY5 up\r\n");
  100. KeyUp&=~KEY5;
  101. }
  102. if(KeyUp&KEY6)
  103. {
  104. Prints("KEY6 up\r\n");
  105. KeyUp&=~KEY6;
  106. }
  107. if(KeyUp&KEY7)
  108. {
  109. Prints("KEY7 up\r\n");
  110. KeyUp&=~KEY7;
  111. }
  112. if(KeyUp&KEY8)
  113. {
  114. Prints("KEY8 up\r\n");
  115. KeyUp&=~KEY8;
  116. }
  117. }
  118. }
  119. }

如果一切正确,在串口调试助手上(波特率9600 数据位8 停止位1 无硬件流控制)将会显示如下所示信息。
其中日期和时间可能不一样,这取决于当前的编译时间。

2.12 本章小结

   本章内容的必要性
















第一章 USB概述及协议基础 1 1.1 USB是什么 1 1.2 USB的特点 1 1.3 USB的拓扑结构 2 1.4 USB的电气特性 5 1.5 USB的线缆以及插头、插座 5 1.6 USB的插入检测机制 7 1.7 USB的描述符及其之间的关系 9 1.8 USB设备的枚举过程 10 1.9 USB的包结构及传输过程 11 1.9.1 USB包的结构及包的分类 11 1.9.2 令牌包 13 1.9.3 数据包 14 1.9.4 握手包 14 1.9.5 特殊包 15 1.9.6 如何处理数据包 15 1.10 USB的四种传输类型 16 1.10.1 USB事务 16 1.10.2 批量传输 16 1.10.3 中断传输 18 1.10.4 等时传输(同步传输) 19 1.10.5 控制传输 20 1.10.6 端点类型与传输类型的关系 21 1.10.7 传输类型与端点支持的最大包长 21 1.11 本章小结 21 第二章 硬件系统设计 1 2.1 方案以及芯片的选定 1 2.2 D12引脚功能说明 2 2.3 D12与89S52的连接 4 2.4 串口部分电路 6 2.5 按键部分 7 2.6 指示灯部分 7 2.7 IDE接口部分 8 2.8 单片机部分 8 2.9 元件安装 8 2.10 电路调试 11 2.11 测试程序编写调试 12 2.11.1 建立一个工程 12 2.11.2 为工程添加源文件 14 2.11.3 KEIL工具栏及仿真介绍 15 2.11.4 按键驱动的编写 18 2.11.5 串口驱动的编写 24 2.11.6 PDIUSBD12读写函数及读ID的实现 28 2.12 本章小结 33 第三章 USB鼠标的实现 1 3.1 USB鼠标工程的建立 1 3.2 USB的断开与连接 1 3.3 USB中断的处理 4 3.4 读取从主机发送到端点0的数据 6 3.5 USB标准请求 12 3.5.1 USB标准设备请求的结构 13 3.5.2 GET_DESCRIPTOR请求 15 3.5.3 SET_ADDRESS请求 16 3.5.6 SET_CONFIGURATION请求 16 3.6 设备描述符的实现 17 3.7 设备描述符的返回 20 3.8 设置地址请求的处理 30 3.9 配置描述符集合的结构 32 3.9.1 配置描述符的结构 32 3.9.2 接口描述符的结构 33 3.9.3 端点描述符的结构 33 3.9.4 HID描述符的结构 34 3.10 配置描述符集合的实现以及返回 35 3.11 字符串及语言ID请求的实现 39 3.12 设置配置请求的实现 45 3.13 报告描述符的结构及实现 48 3.14 报告的返回 54 3.15 Bus Hound工具的简介 57 3.16 本章小结 59 第四章 USB键盘的实现 1 4.1 USB键盘工程的建立 1 4.2 设备描述符的实现 1 4.4 配置描述符集合的实现 2 4.4.1 配置描述符 3 4.4.2 接口描述符 3 4.4.3 HID描述符 3 4.4.4 端点描述 3 4.5 字符串描述符 6 4.6 报告描述符 6 4.7 输入和输出报告的实现 10 4.8 USB键盘实例的测试 13 4.9 再谈USB HID的报告描述符 14 4.10 带鼠标功能的USB键盘(方法一) 16 4.11 带鼠标功能的键盘(方法二) 22 4.12 多媒体USB键盘 29 4.13 本章小结 34 第五章 用户自定义的USB HID设备 1 5.1 MyUsbHid工程的建立 1 5.2 描述符的修改 1 5.3 报告的实现 3 5.4 对用户自定义的USB HID设备的访问 5 5.5 访问HID设备时所用到的相关函数 5 5.5.1 获取HID设备的接口类GUID的函数 5 5.5.2 获取指定类的所有设备信息集合的函数 6 5.5.3 从设备信息集合中获取一个设备接口信息的函数 6 5.5.4 获取指定设备接口详细信息的函数 7 5.5.5 打开设备的函数 8 5.5.6 获取HID设备属性的函数 8 5.5.7 从设备读取数据的函数 9 5.5.8 往设备写数据的函数 9 5.5.9 通过控制端点0读取报告的函数 10 5.5.10 通过控制端点0发送报告的函数 10 5.5.11 关闭设备的函数 10 5.5.12 需要包含的库文件 11 5.6 访问USB HID设备的上位机软件的实现 11 5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值