USB设备开发学习(四)
从本篇开始就要研究USB设备开发硬件部分的知识,本系列硬件部分文章的学习案例来源于《圈圈教你玩USB》。
准备工作
要做硬件开发,第一步肯定都是硬件设计,做硬件设计就包含了设计电路图,布线,出板子,焊接等步骤。
不过对于新手来说,不建议从第一步做起,建议直接买一个成品设备,然后把软件开发部分的知识搞懂后,再尝试自行进行硬件设计的工作。
因此,本篇文章将从单片机开发开始进行学习,后续文章会补上硬件设计的内容。在此前提下,第一步变为需要再淘宝/咸鱼之类的网站上购买相关开发版,可以搜索关键字:圈圈教你usb开发板
,这里不提供购买链接,请自行解决开发板的问题,如果不想购买现成的开发板,并且对自己的动手能力有信心,可以参考本系列文章后续,硬件设计的文章。
本文案例中使用的设备如下图所示,包含运费花费了57¥(个人自行设计打板的总花费并不比购买成品低):
在购买了成品单片机后,还可以从商家那获取该单片机的原理图,如下图所示:
还需要了解两个主要芯片的型号,以便于搜索芯片相关文档,其中51单片机芯片的型号为:STC89C52RC
,USB芯片的型号为:PDIUSBD12
,知道芯片型号后可以使用搜索引擎获取相关文档,后期开发的时候需要参考相关文档。
如果没做过单片机开发的,可以把单片机理解成一个集成了CPU,RAM,ROM的芯片,我们后面进行的开发工作就是用来控制单片机运行,编译出来的程序需要写入(常用说法是用下载)到单片机的ROM当中,不同的单片机下载的方式不一样。对于STC89C52RC
单片机来说,可以直接通过TTL串口来进行下载程序到单片机中。
因此,还需要准备一个串口线,因为准备的开发板设计了RS-232串口母口,所以可以准备一个RS-232串口公口转USB线。或者直接用单片机的TTL串口,就需要准备一个TTL串口转USB的设备。
开发环境
大部分情况下,开发单片机用的都是Windows系统,所以绝大部分好用的工具都是Windows程序。但是,我还是喜欢在Mac系统下做开发工作,经过研究,搭建了Mac下的单片机开发环境。
首先安装VSCode,再安装PlatformIO IDE
[1]插件,一个轻量的单片机开发环境就搭好了。
在本文的样例中,需要修改开发目录下的platformio.ini
,按以下示例进行修改:
1 | ; PlatformIO Project Configuration File |
按照上述过程进行环境搭建,不需要再额外的按照编译器,PlatformIO IDE
会自带编译器,使用的编译工具叫sdcc。下载器(把编译好后的程序写入单片机的工具)使用的叫stcgal[2],PlatformIO IDE
也可以一起安装好,但是有时候需要单独使用stcgal,如果在终端中直接使用PlatformIO IDE
中安装的stcgal会比较麻烦,步骤如下:
1 | $ source ~/.platformio/penv/bin/activate |
所以建议自行使用pip install stcgal
来安装该工具,这样就可以在终端中直接使用stcgal
命令了。
测试串口
设备连接图如下所示:
对照着原理图,假设TTL转USB设备为A,USB开发板为B,那么连接如下所示:
- A的GND连B的任意一个GND。
- A的5V连B的任意一个VCC。
- A的TXD连B单片机的RXD。
- A的RXD连B单片机的TXD。
B单片机的RXD和TXD口,可以参见原理图中J8和J3的10/11口。
如果一切正常,可以在/dev
目录下发现/dev/tty.usbserial-0001
文件,不过会由于使用的TTL转USB的设备不同,导致生成的文件不一样,但是文件都会处于/dev
目录下,文件名一般有tty
, usb
等关键字。通过插拔操作,查看/dev
目录下的文件变动,也是一个方法。
如果存在/dev/tty.usbserial-0001
文件的情况下,运行以下命令,可以查看单片机是否能正常工作:
1 | $ stcgal -P stc89 -p /dev/tty.usbserial-0001 |
第一个单片机程序
一般情况下,我们学习编程语言写的都是Hello World
,但是在单片机开发中,实现一个Hello World
是比较困难的。首先你得需要一个屏幕,其次你得编写该屏幕的驱动,才能在屏幕上输出Hello World
。再简单点,就是通过串口输出Hello World
,但是也得编写一个串口驱动。
第一个程序就编写驱动是非常不友好的,对于我来说,编写第一个程序的目的是为了走通开发的主要流程,能正常编译,编译的程序能正常下载到单片机中,并且单片机能正常运行。所以我们可以降低一下要求,第一个程序,我们的目标是点亮一盏LED灯。
通过原理图可以看出,单片机的P20
到P27
口连接到了LED1
-LED8
这8盏LED灯。这8盏LED灯的另一头连接到了1k欧姆的排阻(RP1)上,而排阻连接到了VCC电源上。所以当P20输出0的情况下,LED1电路就连通了,LED1灯就会发光。
现在,我们来编写第一个程序,让P20输出为0,代码如下所示:
1 | // src/main.c |
通过PlatformIO IDE
创建的项目目录如下所示:
1 | $ ls -a |
由于VSCode装了PlatformIO IDE
插件,所以在打开了PlatformIO IDE
项目的情况下,编写好代码后,在左下角有一个☑️图标,点击就可以编译编写好的程序,也可以使用快捷键:shift + cmd + b
。
编译好后,可以点击build图标右边的➡️图标,表示下载编译好的程序到单片机中。在输出行看到Cycling power: done
时,重新拔插VCC
线,就可以下载程序到单片机中了。
如果一切正常,在下载结束后,就可以看到单片机中LED1灯常亮。
在第一个程序写完后,可以去看一下8051.h
头文件的内容,里面对51单片的各个端口还有寄存器做了宏定义,这样让我们可以很方便的控制单片机的各个端口。比如P2_0
就表示原理图中的P20
端口,大小为1bit,P2
表示的是单片机的P20
到P27
端口,大小为1byte。
不同架构的单片机使用的头文件是不一样的,可以通过搜索引擎或者GPT根据芯片型号来得知该时候哪个头文件,搜索/询问的时候记得带上sdcc
关键词。
第二个程序——定时器中断
第二个程序我们来了解一下单片机的定时器中断,不同单片机的定时器中断实现不一样,这个时候需要参考单片机的相关文档,请通过芯片型号+pdf关键字,自行使用搜索引擎获取芯片文档。
首先,需要编写一个定时器的初始化函数,代码如下所示:
1 | //定时器0初始化 |
通过直接对寄存器赋值的方式来对单片机进行配置,各个寄存器表示的内容可以参考文档,比如TMOD
寄存器的功能描述如下图所示:
TR0
寄存器属于TCON
寄存器的比特位,功能描述如下图所示:
ET0
寄存器属于IE
中断寄存器的比特位,功能描述如下图所示:
通过上面三个寄存器的功能描述,可以看出Timer0Init
函数的作用有以下几个:
- 开启定时器0,设置模式1,为16位定时器。
- 16位定时器使用的计数器为8bit的TL0和8bit的TH0,那么最大有
65536
次计数 - TL0和TH0组合成了定时器的计数器T0,每个工作周期,T0 += 1,当T0溢出时,设置TF0寄存器为1,触发中断。
定时器有一个重要的参数,那就是时间,表示定时器多长时间一个循环。通过上面的总结,我们可以知道,TL0和TH0寄存器就是用来控制循环时间的寄存器。根据文档中一个计算示例,我们来算算该单片机的时间参数该如何设置,如下图所示:
通过USB开发版的原理图,可以看出单片机的X1/X2端口外接了外部晶振,该晶振的频率为22.1184MHz
,那么计算一下一个机器周期:12 / 22118400 ≈ 0.54μs
。
最大循环时间:65536 * 0.54 ≈ 35389.44μs ≈ 35ms
。
假设定义循环时间为20ms
,那么可以求得T0的计数次数为:20 * 1000 / 0.54 = 36864
。
对计数次数取反,就是TL0/TH0寄存器的设置值:65536 - 36864 = 0x7000
,那么需要设置:TL0 = 0x00, TH0 = 0x70
时间算出来了,那么就需要开始实现定时器的功能了,在本次的测试案例中,我们定义一个走马灯功能,我们有8个LED灯,灯从左向右循环亮起,间隔时间1s,实现代码如下所示:
1 |
|
最后,再简单写一个main函数,就可以编译程序了,main函数如下所示:
1 | void Timer0Isr(void) __interrupt 1; //在SDCC编译器中,如果不声明中断,中断不会生效 |
这样,一个走马灯的程序就开发完了,接着进行编译,下载到单片机中,就可以看到LED灯以1s的间隔,从左往右依次亮起。
第三个程序——TTL串口中断
STC89C52RC
单片机自带TTL串口,可以通过该串口下载程序到单片机中,同样也可以使用串口与单片机通信。
首先来看一下串口的初始化函数,代码如下所示:
1 |
|
初始化的各个寄存器同样是通过看芯片文档可以理解各自的用途,如果文档里写的比较简单,可以单独搜索该寄存器,或者询问GPT,都是很容易理解作用的寄存器。
但是有几个注意事项:
- Fclk为晶振的频率,BitRate为设置的串口的波特率,在实际开发的过程中发现,可能是为了节省空间,SDCC在编译的过程中把整型都默认设置为short。对于频率和波特率,
short
型明显长度不够,所以需要在整型结尾加上UL
表示unsigned long
类型值。 PCON |= 0x80
实际表示的是SMOD = 1
,由于在计算TH1
值的时候使用了除法,那么就会出现除不尽的情况,而TH1
为1字节的整型,所以在遇到该情况时,可以设置SMOD=1
,就有可能可以除尽了。- 当
SMOD=1
时,TH1
的计算公式为:256 - Fclk/(BitRate*12*16)
,当SMOD=0
时,TH1
的计算公式为:256 - Fclk/(BitRate*32*16)
接下来就是编写串口的中断函数和串口的读写函数,代码如下所示:
1 | volatile char Sending = 0; // 发送标志 |
接下来可以简单编写一个main函数,代码如下所示:
1 | void UARTIsr() __interrupt 4; |
编译程序,下载到单片机中,这个时候就可以和USB开发版进行串口通信,波特率为9600,模式为8N1。每次reset单片机,都可以在串口中接收到Hello World!
字符串,并且可以看到输入字符的回显,如下图所示:
第四个程序——检测PDIUSBD12芯片是否正常
学完了前面三个程序后,可以说对单片机开发是入门了,能进行以下几种基础操作:控制端口输出,编写中断函数,通过uart口输出调试信息。
接下来第四个程序,简单的来说,就是让单片机与其他外部芯片进行通信。这个时候我们需要参考原理图,查看PDIUSBD12
芯片的哪些引脚和单片机相连,还需要参考D12
芯片的参考文档来编写交互代码,简单的来说就两种函数:单片机传输数据到D12芯片的写函数,单片机获取D12芯片返回数据的读函数。
首先,参考D12
文档和USB开发版原理图,我们来设置一些宏定义,代码如下所示:
1 | // A0值的宏定义 |
接着根据下面的并行接口时序图来编写读写函数,时序图如下所示:
代码如下所示:
1 | // D12 写命令 |
接着,通过文档资料发现存在一个ReadID = 0xFD
命令,可以获取D12
芯片的ID编码,以此来判断芯片是否能正常工作,ID
值为固定的0x1012
。根据该信息,编写以下代码:
1 | // PDIUSBD12芯片读ID命令 |
获取到ID值后,打算通过串口把ID值输出,所以需要编写一个输出整型的函数,代码如下所示:
1 | __code uint8 HexTable[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; |
最后,就是编写main函数,读取D12
芯片的ID并且输出,代码如下所示:
1 | void main() |
编译以上代码,并且下载到单片机当中,得到结果如下图所示:
得到以上结果,说明D12芯片一切正常,可以进行后续开发工作。后续的USB开发工作将在后续文章中继续讲解。
参考链接
USB设备开发学习(四)