想实现PPC通过USB数据线与台式电脑(虚拟出一个串口)间的通讯
最近遇到编程问题 实在没办法了
我手上有一个pocket pc 2003(惠普掌上电脑HP iPAQ rz1717)芯片是S3C2410
我想实现PPC通过USB数据线与台式电脑间的通讯,通过在台式电脑虚拟一个串口来通讯,这一步是为了调试上位机程序。
想通过第一步了解一下这方面的原理,最终实现PPC与Arduino Nano Mega328控制器通讯,来控制舵机;
PPC上有上位机程序,并虚拟出一串口,通过USB数据线与Arduino Nano Mega328控制器通讯
不知道谁有例程 或者了解这方面的信息。。。
我曾下载了一个例程(USB Virtual COM S3C24XX),几乎把所有代码都注释说明了,但还是搞不懂他是如何通过USB把数据发送至PC的,mai()函数如下
/****************************************************************
NAME: usbmain.c
DESC: endpoint interrupt handler:端点中断处理程序
USB init jobs:USB初始化作业
HISTORY:
Mar.25.2002:purnnamu: ported for S3C2410X:移植到S3C2410X
Mar.27.2002:purnnamu: DMA is enabled:DMA已启用
INC目录下的文件:
2410addr.h/2410addr.inc/2410lib.h/2410slib.h/Def.h/Memcfg.inc/Option.inc/Uart0.h/mmu.h/Option.h
Def.h:基本数据类型重定义头文件,在定义数据类型时尽量使用U32,U16,S32,S16,U8,S8等类型,以增强程序的可移植性。
Option.h:硬件系统重要设置头文件,如果要修改系统的工作频率、总线宽度,一些重要的地址的值可在本文件中修改。
2410addr.h:2410的寄存器的地址宏定义头文件,方便使用。
2410addr.inc:注意,在汇编语言中,用此文件。
2410lib.h:包含调试时常用函数,还有一些其他常用函数的头文件。
2410slib.h:包含MMU相关函数的头文件。
SRC目录下的文件:
2410init.s:2410初始化启动程序,由汇编语言写成。
2410lib.c:描述了2410的调试常用函数的原型,用C语言写成。
2410slib.s:包含汇编语言写的MMU相关的程序代码。
mmu.c:包含MMU相关的程序代码。
Uart0.c:包含了串口的常用函数原型,用C语言写成。
****************************************************************/
#include <string.h>
#include <stdarg.h>//C语言中C标准函数库的头文件,stdarg是由standard(标准) arguments(参数)简化而来,主要目的为让函数能够接收可变参数
#include "option.h"
#include "2410addr.h"
#include "2410lib.h"
#include "def.h"
#include "2410usb.h"
#include "usbmain.h"
#include "usblib.h"
#include "usbsetup.h"
#include "usbout.h"
#include "usbin.h"
/**************************
Some PrepareEp1Fifo() should be deleted
一些PrepareEp1Fifo()应该被删除
**************************/
void UsbdMain(void)
{
int i;
U8 tmp1;
U8 oldTmp1=0xff;
//ChangeUPllValue(0x48,0x3,0x2); //UCLK=48Mhz :设置UCLK的值。给USB供节拍。
//ChangeUPllValue(40,1,2); //UCLK=48Mhz setting in init.s
InitDescriptorTable(); // 初始化设备描述符,比如设备描述符,配置描述符等,在usb控制传输的时候返回给设备。这个函数在usbsetup.c中
//ResetUsbd();
ConfigUsbd(); //这个函数是个封装函数,第一次配置调用他,并且开启中断。在重置USB的过程中调用的是ReconfigUsbd(),这个函数也在usblib.c中
//PrepareEp1Fifo();//准备发送数据FIFO队列(端点1的,这里没有用到)
//DetectVbus(); //not used in S3C2400X
#if 0
while(1)
{
if(DbgPrintfLoop())continue;//通过串口逐个字节输出数据
Delay(5000);
if((i++%2)==0)Led_Display(0x8);//Led_Display()位于2410lib.h
else Led_Display(0x0);
}
#endif
/*20170918help:关于“#if 0/#if 1 ... #endif”的作用
#if 0
code
#endif
C标准不提供C++里的“//”这样的单行风格注释而只提供“/星号 星号/”这样的块注释功能,我们通常使用它写代码中说明性的注释文字(注释作用)以及在调试时关闭某段代码对编译器的可见
性(屏蔽作用),当然,这里所谓的“注释作用”和“屏蔽作用”是我们从功能上下的主观定义,对预处理器而言,两者并无任何区别。
(1)code中定义的是一些调试版本的代码,此时code完全被编译器忽略。如果想让code生效,只需把#if 0改成#if 1
(2)#if 0还有一个重要的用途就是用来当成注释,如果你想要注释的程序很长,这个时候#if 0是最好的,保证不会犯错误。(但是林锐的书上说千万不要把#if 0 来当作块注释使用)
#if 1可以让其间的变量成为局部变量。
(3)这个结构表示你先前写好的code,现在用不上了,又不想删除,就用这个方法,比注释方便。
*/
}
/*******************************************************************usb设备中断服务程序**********************************************************/
/*20170928help:关于 __irq
在ADS编译器中,“__irq”专门用来生命IRQ中断服务程序,如果用__irq来声明一个函数,那么该函数表示一个IRQ中断服务程序,编译器会自动在该函数内部增加中断现场保护的代码
armcc是ARM C编译器。这个编译器通过了 Plum Hall C Validation Suite,为ANSI C的一致性测试。armcc 用于将用ANSI C编写的程序编译成32位ARM指令代码。因为armcc是我们最
常用的编译器,所以对此作一个详细的介绍。
*/
void __irq IsrUsbd(void)
{
U8 usbdIntpnd,epIntpnd;
U8 saveIndexReg=rINDEX_REG; //读取索引寄存器值(rINDEX_REG地址在2410addr.h定义)
usbdIntpnd=rUSB_INT_REG;// 读取USB 中断寄存器(rUSB_INT_REG地址在2410addr.h定义)
epIntpnd=rEP_INT_REG;// 读取端点中断寄存器(rEP_INT_REG地址在2410addr.h定义)
//DbgPrintf( "[INT:EP_I=%x,USBI=%x]",epIntpnd,usbIntpnd );
if(usbdIntpnd&SUSPEND_INT)//总线上超过 3ms 没有活动信号引起中断
{
rUSB_INT_REG=SUSPEND_INT;//清除中断标志位
DbgPrintf( "<SUS]");
}
if(usbdIntpnd&RESUME_INT)
{
rUSB_INT_REG=RESUME_INT; //恢复引起的中断
DbgPrintf("<RSM]");
}
/*01:RESET_INT复位中断
主机集线器检测到设备,主机发总线复位。这个复位与USB上电复位和系统复位是不同的。这个是SIE根据总线状态通知用户的一种复位。设备产生复位中断,如何处理由设备固
件程序决定。
SIE (Serial Interface Engine)是 USB 外设最重要的硬件组成部分之一,它主要由四部分组成:
1) 硬件上用来完成 NRZI 编/译码和加/去填充位操作的,NRZI/Bit Buffing 和NRZO/Bit Unstuffing 的部分。
2) 硬件上产生资料的 CRC 校验码并对资料包进行 CRC 校验的 CRC check & Generator 部分。
3) 用来将并行资料转化成 USB 串行资料的并/串转换部分( Packet Encode ),将主机发送的 USB 资料包转化成可以识别的并行资料的串/并转换部分( Packet Decode )。
4) 检测和产生 SOP (即每个资料包的同步字段)和 EOP 信号的部分
*/
if(usbdIntpnd&RESET_INT)//接收到复位信号引起的中断
{
DbgPrintf( "<RST]");
//ResetUsbd();
ReconfigUsbd();//位于usblib.c
/*重新配置 USB device:
01禁止设备进入挂起模式( 正常模式)
02端点0最大数据包
03对端点0清除SETUP_END和OUT_PKT_RDY状态
04接下来对具体端点进行操作.如果该端点已初始化,则读取IN_CSR2_REG和OUT_CSR2_REG寄存器的值并保存.
04.1接着设置IN_CSR2_REG,端点方向为IN,禁止的DMA中断.
设置IN_CSR1_REG清除data toggle bit,包的PID(标识符段)包含DATA0.
04.2然后设置OUT方向,
设置OUT_CSR1_REG FLUSH FIFO,清除data toggle bit,数据包顺序设为DATA0,
设置OUT_CSR2_REG,禁止DMA中断.
04.3如果初始化状态为TRUE,则恢复之前保存的寄存器内容.
05最后清除所有EndPoint中断
rEP_INT_REG=EP0_INT|EP1_INT|EP2_INT|EP3_INT|EP4_INT;
rUSB_INT_REG=RESET_INT|SUSPEND_INT|RESUME_INT;
06所有端点中断使能
rEP_INT_EN_REG=EP0_INT|EP1_INT|EP3_INT;// 中断使能寄存器
rUSB_INT_EN_REG=RESET_INT;// USB 复位中断使能
07端点0 状态设置为初始化
ep0State=EP0_SETUP_STATE;
*/
rUSB_INT_REG=RESET_INT;//清除复位标志RESET_INT should be cleared after ResetUsbd().
}
//以上三个是USB设备中断(SUSPEND_INT,RESUME_INT,RESET_INT位于2410usb.h),主要用到的是复位中断,当GPC5引脚为高时,USB 插入主机,就会产生这个中断
/*02:设备复位中断之后
正常情况下,在复位中断处理以后的下一次中断应该执行到这里
*/
if(epIntpnd&EP0_INT) //如果是端点 0 中断
{
rEP_INT_REG=EP0_INT;//清除所有端点0等待位
Ep0Handler(); //进入控制传输中断处理程序,在设备枚举的过程中使用
}
if(epIntpnd&EP1_INT)//如果是端点 1 中断
{
rEP_INT_REG=EP1_INT;
Ep1Handler();//进入 Ep1 中断处理程序(BULK IN)
}
if(epIntpnd&EP2_INT)//如果是端点 2 中断
{
rEP_INT_REG=EP2_INT;
DbgPrintf("<2:TBD]"); //not implemented yet
//Ep2Handler();
}
if(epIntpnd&EP3_INT)//如果是端点 3 中断
{
rEP_INT_REG=EP3_INT;
Ep3Handler();//进入 Ep3 中断处理程序(BULK OUT)
}
if(epIntpnd&EP4_INT)//如果是端点 4 中断
{
rEP_INT_REG=EP4_INT;
DbgPrintf("<4:TBD]"); //not implemented yet
//Ep4Handler();
}
//以上5个端点中断采用相应处理程序(EP0_INT,EP1_INT,EP2_INT,EP3_INT,EP4_INT位于2410usb.h)
ClearPending(BIT_USBD);//USB 设备中断处理完毕,清除中断标志位(BIT_USBD位于2410addr.h);ClearPending()在2410addr.h定义
rINDEX_REG=saveIndexReg;//恢复索引寄存器原来的值(恢复现场)
}
/******************* Consol printf for debug 控制台printf进行调试*********************/
#define DBGSTR_LENGTH (0x1000)
U8 dbgStrFifo[DBGSTR_LENGTH];
volatile U32 dbgStrRdPt=0;
volatile U32 dbgStrWrPt=0;
void _WrDbgStrFifo(U8 c)
{
dbgStrFifo[dbgStrWrPt++]=c;
if(dbgStrWrPt==DBGSTR_LENGTH)dbgStrWrPt=0;
}
/*******************************************************************************
* Function Name : DbgPrintfLoop
* Description : 从串口发送一个字节;依靠外部函数循环发送:main()→while 1{→DbgPrintfLoop()}
*******************************************************************************/
int DbgPrintfLoop(void)
{
if(dbgStrRdPt==dbgStrWrPt)return 0;
Uart_SendByte(dbgStrFifo[dbgStrRdPt++]);//Uart_SendByte()从串口发送一个字节;位于2410lib.h
if(dbgStrRdPt==DBGSTR_LENGTH)dbgStrRdPt=0;
return 1;
}
#if 0//我把0改成1,就可以让系统只编译A部分代码而不编译B部分代码,这样改起来就很方便啊。如果发行代码和调试代码是不同的,这是一个很常见的做法。
void DbgPrintf(char *fmt,...)//...表示可变参数(多个可变参数组成一个列表,后面有专门的指针指向他),不限定个数和类型,
{
int i,slen;
va_list ap;//初始化指向可变参数列表的指针;VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件stdarg.h,用于获取不确定个数的参数。
char string[256];
va_start(ap,fmt);
//将第一个可变参数的地址付给ap,即ap指向可变参数列表的开始
//va_start(函数名称,stdarg.h),读取可变参数的过程其实就是在堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程
vsprintf(string,fmt,ap);
//将参数fmt、ap指向的可变参数一起转换成格式化字符串,放string数组中,其作用同sprintf(),只是参数类型不同;vsprintf位于stdarg.h
//vsprintf() 中的 arg 参数位于数组中。数组的元素会被插入主字符串的百分比 (%) 符号处。该函数是逐步执行的。在第一个 % 符号中,插入 arg1,在第二
//个 % 符号处,插入 arg2,依此类推。
slen=strlen(string);
//strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串
//结束符'\0'为止,然后返回计数器值(长度不包含'\0')。位于stdarg.h
for(i=0;i<slen;i++)
_WrDbgStrFifo(string[i]);
va_end(ap);//ap付值为0,没什么实际用处,主要是为程序健壮性
}
#else
void DbgPrintf(char *fmt,...)
{
}
#endif