完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1 项目简介 超市的收银台一般都会放置一台小型打印机,其任务是将顾客的消费金额等信息打印在一张小票上。那么打印机又是如何知道自己该打印那些内容呢?本文以经典的RS232串口通信来模拟收银台电脑与打印机之间的通信工作流程。
为了将所学的串口通信知识与实践相结合,现在通过开发一个实际的项目来掌握串口通信的核心要点及开发流程,实现由理论知识到实际应用的转变,本文对超市微型打印机的通信部分进行研究设计。设计目标是: (1)搭建电脑到打印机之间的RS232串口通信电路; (2)设计串口通信程序,程序运行在打印机控制器STM32F103C8T6中; (3)通过串口调试助手对通信效果进行演示。 2 设计要求 基础部分 串口通信的具体设计要求如下: (1)波特率4800bps,一位起始位,八位有效数据位,一位奇偶校验位; (2)使用串口调试助手发送一个数据打印命令“@PrintData12345678900#”; (3)单片机收到命令后,解析命令,输出数据“1234567890”,然后给上位机发送“数据已成功打印”。 扩展部分 为了加深对串口通信知识的理解和运用,本文提出以下扩展要求并进行实现: (1)每次输出打印信息及提示信息结束后都进行换行显示; (2)系统上电工作时,通过串口通信助手显示系统正常工作的提示信息; (3)当打印指令错误时,输出“instruction error”进行提示; (4)当接收的字符数大于设定的缓冲区大小时,输出“Overflow error”; (5)当出现校验错误时,输出“校验错误,请重新发送命令”。 3 总体设计方案 使用笔记本电脑与STM32单片机进行数据交互,模拟超市电脑与打印机之间的通信过程。笔记本电脑上安装有串口调试助手软件,串口调试助手作为上位机可以实现人机界面交互的功能,同时也是检验串口通信是否正常的工具。 笔记本电脑一般没有串口,但至少有一个USB口。单片机的串口是映射到GPIO上的,本文只需要用到接收、发送引脚,也就是2个GPIO,再加上电源和地,一共4个单片机引脚。笔记本的USB口和单片机串口之间不能直接相连,因为两者的工作电平逻辑不一致,需要通过一个USB转TTL模块进行电平转换,同时笔记本电脑上需要安装配套的USB转串口驱动程序。 系统设计方案 4 设计前的思考和准备 思考: 1、串口的参数设置中有一位校验位,如何实现,又如何体现校验的作用; 2、如何判断串口数据传输的开始、结束; 3、如何判断命令是否符合要求,并取出中间的数据部分; 4、能否直接输出中文。 准备:
硬件连接 2.安装USB转串口驱动程序 本文用到的USB转TTL模块芯片为PL2303HX,驱动程序可在网上进行下载,双击驱动程序按提示完成驱动安装。其他芯片的驱动安装方法类似。 安装USB转串口驱动 驱动安装完成后,插上USB转TTL模块,在设备管理器中选择端口,如果能够看到识别到的驱动程序和端口号,表示驱动安装成功。否则,右键选择更新驱动程序,从我的计算机驱动程序中选择2009年的版本,当出现下图界面,能分配到一个端口号即表示安装成功。其它的芯片模块可能在驱动程序安装完后就能看到端口号,此时不需要进行驱动程序的更新操作。 驱动安装成功示意图 3.下载串口调试助手软件、Keil-MDk软件 串口调试助手网上非常多,可以自行选择,但是要注意,一定要知道这款串口助手对中文字符的编码采用什么格式,网上资源以GB2313居多,也有部分是采用UTF-8格式的,本文选择GB2313编码方式的串口调试助手。 Keil-MDK软件是开发嵌入式的IDE,可从网上下载,建议下载5.28版本,破解操作也可从网上参考,本文不赘述。Keil-MDK编程的编码格式设置一定要与串口调试助手一致,这样才不会出现中文乱码的问题,建议选择GB2313。 MDK-keil编码设置 4.准备一份STM32中文参考手册,便于开发过程中查询串口的使用方法以及相关寄存器的设置。 5.准备一份STM32固件库手册,便于开发程序时查询标准固件库函数使用方法,加快开发效率。 6.找到一份STM32工程模板文件,可以直接从固件库文件中复制,也可以自己创建,省去工程的创建、文件的添加等机械工作。 准备资料 5 本文设计的关键点 5.1 校验位设置 通过查阅STM32中文手册发现,USART_CR1寄存器中的PCE位可以使能奇偶校验位的产生,M位可以设置数据字的长度,如下图所示: USART_CR1寄存器PCE位-决定是否产生校验位 USART_CR1寄存器M位-决定数据字长度 M位和PCE位的值两者共同决定USART帧的格式,一共四种,如下图所示。根据设计要求,本文设置为M位为1,PCE位为1,即字长为9,校验位选择奇校验(也可选择偶校验)。 校验控制串口帧格式 通过查阅STM32固件库手册,复制串口参数设置代码示例,并进行修改,下面展示关键部分的参数设置。 串口初始化参数设置 5.2 串口中断设置 串口通信的数据接收、发送程序一般有中断和查询(也叫轮询)两种方式,相对来说,中断方式对CUP的资源占用较少,响应也更及时,本文使用中断方式接收来自上位机电脑的数据。 翻阅STM32中文手册发现,STM32单片机的串口中断请求事件有以下几种,当表中事件发生时,只要设置了相应的中断使能,都会通过嵌套中断向量(NVIC)的向量表进入到串口中断服务程序中,可以在服务程序中查询相应事件的标志位判断当前是那一种事件发生。为了检测数据的接收、接收完成、奇偶校验出错这三种情况,本文设置的三个串口中断事件,下图中用红框标注。 ①接收数据就绪可读表示对方已经发送了一个字节数据,这个数据现在在硬件接收缓冲区中,并且现在可以读出数据。当第一次发生此事件时代表数据传输的开始。 ②检测到空闲线路表示在对方传输上一字节数据后,又过了一个字节的传输时长,单片机仍然没有接收到数据,可以代表数据发送的结束,也就是完成了一次数据的传输。 ③奇偶校验错表示按照约定的校验规则,当前字节的数据发生了错误。 STM32串口中断事件 (注:表中的TXNE标志应为RXNE) 根据固件库手册和库函数注释,使能上述三个串口中断事件的代码如下: 使能串口中断事件 5.3 串口中断服务程序 根据STM32中文手册,串口中断事件相应的标志位在状态寄存器SR中,在串口中断服务程序中查询这几个标志位的值就可知道发生了什么事件。SR寄存器中的三个标志位介绍如下所示: 串口中断标志位及清除方法 从上表看出,标志位置1时,表示该事件发送,此时单片机可以进行相应操作。但要注意,一定要及时清除标志位(清0操作),否则程序会一直进入中断服务程序。对于RXNE位,当读出DR寄存器中的数据后,硬件自动清0,不用再单独写清0的代码。对于PE位和IDLE为,需由软件进行清0,清0操作为先读SR寄存器,再读DR寄存器。串口1软件清除PE、IDLE标志位代码为: USART1->SR; //先读状态寄存器 USART1->DR; //再读数据寄存器 串口1的中断服务程序名为void USART1_IRQHandler(void),位于stm32f10x_it.c文件中,当然,也可以写在其他位置,但程序名不要改变,同时要处理好各c文件之间依赖关系。 串口中断服务程序 6 程序设计 在本文的程序设计中,要注意以下几个要点。
6.1 主函数 主函数流程图 主函数程序展示图 6.2 串口中断处理函数 串口中断处理函数流程图 接收中断回调函数 空闲中断回调函数 校验出错中断回调函数 6.3 数据解析函数 数据解析函数流程 6.4 串口输出函数 串口输出函数流程 7 结果演示 打开串口调试助手,设置好串口参数,按各种情况进行测试,演示效果如下 演示效果图 8 课堂问题解答 第19组第一个问题:开始和结束错误标志都设置为2会不会分不清? 答:因为指令的开始和结束部分出现错误时都表示这是一个错误的指令,所以程序中将其错误类型都设置为2,这样做确实是无法分清到底是指令的开始部分发生了错误还是结束部分发送错误。如果确实有必要将这两种错误分开,可以考虑在程序中将这两种情况设置不同的错误标志号。 第12组第一个问题:为啥选奇校验和偶校验哪个都对? 答:尚不清楚您说的对是什么意思,但是不管在串口助手中选哪一种校验方式,发送指令后单片机都会返回一段信息,可能是成功打印的信息,也可能是错误的提示信息。在演示中出现了一个细节可能与您的提问相关,因为当时发送的是一个错误的指令,所以不管选择奇校验还是偶校验方式,单片机返回的信息都是“instruction error”,这是由于程序中必须对数据进行解析,当解析结果为指令错误时,其错误码为2,也就是输出“instruction error”。换句话说,当出现多种错误时,优先考虑指令是否错误。当发送一个正确的指令时,在串口助手中选择与程序设计中不同的校验方式,就会输出“校验错误,请重新发送命令”。 第2组第一个问题:自己设定的三个中断有优先级的考虑吗? 答:程序中设置有接收中断、空闲中断、奇偶校验出错中断,但这三个中断本质上都属于串口中断,只不过这是引起串口中断的三种不同事件,当任意一件事件发生时,都会进入同一个串口中断服务函数,可以在中断服务函数中判断对应的标志位是否置1,进而确定是哪一个事件的发生导致进入了串口中断服务函数。因为不可能有两个及以上的事件同时发生,所以这三个中断事件之间不存在优先级的问题。另外,因为整个程序只用到了一个串口中断,不存在其它外设中断源,所以也可以不用对中断优先级进行设置。 第5组第一个问题:可以详细讲解一下中断的过程吗,比如各个中断的优先级,以及如果在主函数解析时中断关闭的方式等 答:一个外设(比如定时器、串口)要发生中断有三个条件,一是使能了外设,可类比为电路中的干路总开关;二是打开了中断使能位,可类比为支路的第一个开关(有多种事件可触发串口中断,可看成是多条支路);三是对应事件的中断标志位置1(一般是硬件自动置1),可类比为支路第二个开关,只有三个开关全部合上,才会进入中断服务函数。 当发生中断时,程序会保存当前的数据和指令执行位置等信息到堆栈,称为现场保护。这个堆栈空间是硬件自动开辟的,数据的保存也是自动完成的,不需要在程序中再进行任何操作。保存完毕后,程序会查询中断向量表,向量表上保存的是各个中断的服务程序的起始地址,通过此地址可跳转到中断服务中执行中断服务体内的程序,当离开中断服务程序时,程序会从堆栈中弹出数据,返回到进入中断前的指令位置,继续执行当前程序。 中断的优先级一般指的是不同中断源之间的优先级别高低,比如程序中存在定时器中断和串口中断,那么这两个中断可能会同时触发,或者正在串口中断服务程序中执行时,定时器中断又被触发。当发生这些情况时,如果没有相应的处理机制,系统将会陷入混乱。由此引入的中断优先级的概念,即将每个中断源都设置一个优先级别,这样,遇到上述情况时,就可以根据优先级的高低来决定当前去处理那一部分的程序。如果优先级一样,或者在程序中没有设置中断优先级,那么系统会根据向量表中默认的优先级进行处理。 STM32中文手册中的第9章可以查看中断的相关信息,串口1中断的默认优先级为44。 STM32通过4位数据对优先级进行编程设置,在程序中设置中断优先级时,有优先级分组、抢占优先级和响应优先级三个参数设置,系统复位后默认的中断优先级优先级分组设置为0,即全部用于响应优先级,不存在抢占优先级。详情见STM32固件库手册,本文不再赘述。在本文程序的主函数中解析数据时,如果要关闭中断,只需要关闭上面提到的三个开关中任意一个即可,最简单的就是直接失能串口外设,其代码为: USART_Cmd(USART1, DISABLE); /* 失能USART1 */ 当输出数据后,可以再次开启串口,进行下一次的接收。 第9组第一个问题:如何编写中断服务函数 答:进入中断服务函数后,首先是对标志位进行判断,确认中断事件,然后是标志位的清除,避免一直进入中断服务函数。接下来有两种思路,一是将发生该中断事件时需要处理的逻辑代码全部放在该程序中,比如数据的保存、LED等的亮灭等等,执行完毕后退出中断服务函数;二是设置一个标志位,然后在主函数的while循环中判断此外部变量标志位的值,进而进行相关操作。 第21组第一个问题:解析数据时关闭中断这样会不会有后续数据的丢失? 答:分两种情况,如果在解析数据时,又从上位机发送了数据到单片机,那么这部分数据会丢失,因为中断已经关闭,无法进入中断服务函数,数据根本无法接收,只会在硬件缓冲区一次又一次的被覆盖。如果在解析数据时没有新的数据传输到单片机,等解析完毕后,中断再次打开时,才有数据传输过来,那么对数据是没有任何影响的。当然,单片机处理数据的数据还是比较快的,如果上一次传输的数据比较少,解析数据的时间花费少,中断可以很快再次打开,那么是感觉不到什么影响的,如果一次传输大量数据,在下一次传输时就可能存在丢失数据的情况,也就是丢包。 第4组第一个问题:当数据进入中断的时候,主函数是在等待吗?如果是的话中断函数比较多,怎么保证当中断结束时主函数能从原来的位置直接运行? 答:当进入中断时,主函数的确是处于等待状态,但第二个问题不需要担心,每一次进入中断时,硬件会自动进行现场保护,也就是开辟堆栈空间,保存当前的状态信息,当然也包括当前指令执行的位置,中断服务函数结束后,数据从堆栈中弹出,程序回到进入中断前的现场,继续执行。整个过程都是硬件自动完成,程序员不用做任何操作。 第5组第二个问题:如果传输数据中,出现终止符怎么办? 答:一般不会在串口助手中发送终止符,字符串终止符(也就是空字符)的Ascii码为0x00,在本文程序中,如果出现终止符,后续数据也是能够接收的,即能够保存在数组中。但是在单片机的串口发送函数中,遇到空字符就会认为数据已经结束,后续的数据不会被发送。遇到这种问题,暂时没有一个好的解决方案,可以考虑修改串口发送函数,不再以是否为空字符为结束标志,而是确定一个明确的数据长度。 第18组第一个问题:程序中字符串的发送是怎么实现的? 答:程序中的字符串发送没有用到任何中断,当然也可以使用中断,在中断服务函数中再调用数据发送函数。库函数void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)可以实现通过串口发送引脚TX,发送1字节数据的功能,第一个参数为串口号,第二个参数为发送的数据,因为STM32中的串口发送数据的字长有8位和9位两种,所以此参数类型为uint16_t,即16位。但是一般8位最常用,因为刚好一个字节,9位的用得较少。发送数据后需要等待当前数据发送完成才能进行下一次的发送,也就是等待发送完成标志位TC置1,代码为: while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET); 为了实现字符串的发送,也就是多个字节连续发送,可定义一个函数,如下: void Send_data(uint8_t *data) { while(*data!=' |