完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
串口通信是串行通信里面的异步方式。串行通信是相对于并行通信来说的。串口是一个事实存在的东西,比如DB9接口。 串口通讯里面的波特率,实际上是比特率。如果这两点你还不是很清楚地话,好好往下看。 通信涉及的几个基础概念 同步通信和异步通信 (1)、同步和异步的区别:简单来说就是发送方和接收方按照同一个时钟节拍工作就叫同步,发送方和接收方没有统一的时钟节拍、而各自按照自己的节拍工作就叫异步。 (2)、同步通信中,通信双方按照统一节拍工作,所以配合很好;一般需要发送方给接收方发送信息同时发送时钟信号,接收方根据发送方给它的时钟信号来安排自己的节奏。同步通信用在通信双方信息交换频率固定,或者经常通信时。带时钟同步信号传输。如-SPI,IIC通信。 (3)、异步通信又叫异步通知。在双方通信的频率不固定时(有时 3ms 收发一次,有时 3 天才收发一次)不适合使用同步通信,而适合异步通信。异步通信时接收方不必一直在意发送方,发送方需要发送信息时会首先给接收方一个信息开始的起始信号,接收方接收到起始信号后就认为后面紧跟着的就是有效信息,才会开始注意接收信息,直到收到发送方发过来的结束标志。异步通信:不带时钟同步信号。如·UART(通用异步收发器),单总线。 电平信号和差分信号 (1)、电平信号和差分信号是用来描述通信线路传输方式的。也就是说如何在通信线路上表达 1 和 0. (2)、电平信号的传输线中有一个参考电平线(一般是 GND),然后信号线上的信号值是由信号线电平和参考电平线的电压差决定。 (3)、差分信号的传输线中没有参考电平,所有都是信号线。然后 1 和 0 的表达靠信号线之间的电压差。 总结:电平信号的 2 根通信线之间的电平差异容易受到干扰,传输容易失败;差分信号不容易受到干扰因此传输质量比较稳定,现代通信一般都使用差分信号,电平信号几乎没有了。总结 2:看起来似乎相同根数的通信线下,电平信号要比差分信号要快;但是实际还是差分信号快,因为差分信号抗干扰能力强,因此 1 个发送周期更短。 并行接口和串行接口 (1)、串行、并行主要是考虑通信线的根数,就是发送方和接收方同时可以传递的信息量的多少 (2)、譬如在电平信号下,1 根参考电平线+1 根信号线可以传递 1 位二进制;如果我们有 3根线(2 根信号线+1 根参考线)就可以同时发送 2 位二进制;如果想同时发送 8 位二进制就需要 9 根线。 (3)、在差分信号下,2 根线(彼此差分)可以同时发送 1 位二进制;如果需要同时发送 8 位二进制,需要 16 根线。 总结:听起来似乎并行接口比串行接口要快(串行接口一次只能发送 1 位二进制,而并行接口一次可以发送多位二进制)要更优秀;但是实际上串行接口才是王道,用的比较广。因为更省信号线,而且对传输线的要求更低、成本更低;而且串行时可以通过提高通信速度来提高总体通信性能,不一定非得要并行。 总结:异步、串行、差分,譬如 USB 和网络通信更胜一筹。 串口通信涉及的基础概念 异步、电平信号、串行 (1)、异步:串口通信的发送方和接收方之间是没有统一的时钟信号的。 (2)、电平信号:串口通信出现的时间较早,速率较低,传输的距离较近,所以干扰还不太明显,因此当时使用了电平信号传输。后期出现的传输协议都改成差分信号传输了。 (3)、串行通信:串口通信每次同时只能传输 1 个二进制位 RS232 电平和 TTL 电平 (1)电平信号是用信号线电平减去参考线电平得到电压差,这个电压差决定了传输值是 1 还是 0. (2)在电平信号时多少 V 代表 1,多少 V 代表 0 不是固定的,取决于电平标准。譬如 RS232电平中-3V~-15V 表示 1;+3~+15V 表示 0;TTL 电平则是+5V 表示 1,0V 表示 0. (3)不管哪种电平都是为了在传输线上表示 1 和 0.区别在于适用的环境和条件不同。RS232的电平定义比较大,适合干扰大、距离远的情况;TTL 电平电压范围小,适合距离近且干扰小的情况。 (4)我们台式电脑后面的串口插座就是 RS232 接口的,在工业上用串口时都用这个,传输距离小于 15 米;TTL 电平一般用在电路板内部两个芯片之间。 (5)对编程来说,RS232 电平传输还是 TTL 电平是没有差异的。所以电平标准对硬件工程师更有意义,而软件工程师只要略懂即可。(把 TTL 电平和 RS232 电平混接是不可以的) 波特率 (1)衡量通讯性能的一个非常重要的参数就是通讯速率,通常以比特率(Bitrate)来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。容易与比特率混淆的概念是“波特率”。(Baudrate),它表示每秒钟传输了多少个码元。而码元是通讯信号调制的概念,通讯中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如常见的通讯传输中,用 0V表示数字 0,5V 表示数字 1,那么一个码元可以表示两种状态 0 和 1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通讯传输中,有 0V、2V、4V以及 6V分别表示二进制数 00、01、10、11,那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率。譬如每秒种可以传输 9600 个二进制位(传输一个二进制位需要的时间是 1/9600秒,也就是 104us),比特率就是 9600.但因为一个码元都是表示两种状态,所以比特率=波特率。也通常说波特率就是 9600 (2)串口通信的波特率不能随意设定,而应该在一些值中去选择。一般最常见的波特率是 9600或者 115200.为什么波特率不可以随便指定?主要是因为:第一,通信双方必须事先设定相同的波特率这样才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的。第二,常用的波特率经过长久发展,就形成了共识,大家常用就是 9600 或者 115200. 起始位、数据位、奇偶校验位、停止位 (1)串口通信时,收发是一个周期一个周期进行的,没周期传输 n 个二进制位。这一个周期就叫做一个通信单元,一个通信单元是由:起始位+数据位+奇偶校验位+停止位组成的。 (2)起始位表示发送方要开始发送一个通信单元;数据位是一个通信单元中发送的有效信息位;奇偶校验位是用来校验数据位,以防止数据位出错的;停止位是发送方用来表示本通信单元结束标志的。 (3)起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。 (4)数据位是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(一般可选的有 6、7、8、9,99%情况下我们都是选择 8 位数据位。因为我们一般通过串口发送的文字信息都是 ASCII 码编码的,而 ASCII 码中一个字符刚好编码为 8 位。) (5)奇偶校验位是用来给数据位进行奇偶校验(把待校验的有效数据逐个位的加起来,总和为奇数奇偶校验位就为 1,总和为偶数奇偶校验位就为 0)的,可以在一定程度上防止位反转。
总结:串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数包括:波特率、数据位、奇偶校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择) 串口通信的基本原理 全双工、半双工及单工通讯 (1)单工就是单方向,双工就是双方同时收发,同时只能单方向但是方向可以改变叫半双工 (2)如果只能 A 发 B 收则单工,A 发 B 收或者 B 发 A 收(两个方向不能同时)叫半双工,A发 B 收同时 B 发 A 收叫全双工。 三根通信线:Rx Tx GND (1)任何通信都要有信息传输载体,或者是有线的或者是无线的。 (2)串口通信是有线通信,是通过串口线来通信的。 (3)串口通信线最少需要 2 根(GND 和信号线),可以实现单工通信,也可以使用 3 根通信线(Tx、Rx、GND)来实现全双工。 收发双方事先规定好通信参数 (1)串口通信属于基层基本性的通信规约,它自己本身不会去协商通信参数,需要通信前通信双方事先约定好通信参数(波特率、数据位、奇偶校验位、停止位等) (2)串口通信的任何一个关键参数设置错误,都会导致通信失败。譬如波特率调错了,发送方发送没问题,接收方也能接收,但是接收到全是乱码··· 信息以二进制流的方式在信道上传输 (1)、串口通信的发送方每隔一定时间(时间固定为 1/波特率,单位是秒)将有效信息(1或者 0)放到通信线上去,逐个二进制位的进行发送。 (2)接收方通过定时(起始时间由读到起始位标志开始,间隔时间由波特率决定)读取通信线上的电平高低来区分发送给我的是 1 还是 0。依次读取数据位、奇偶校验位、停止位,停止位就表示这一个通信单元(帧)结束,然后中间是不定长短的非通信时间(发送方有可能紧接着就发送第二帧,也可能半天都不发第二帧,这就叫异步通信),下来就是第二帧····· 总结:第一,波特率非常重要,波特率错了整个通信就乱套了;数据位、奇偶校验位、停止位也很重要,否则可能认不清数据。 第三,通过串口不管发数字、还是文本还是命令还是什么,都要先对发送内容进行编码,编码成二进制再进行逐个位的发送。 (3)串口发送的一般都是字符,一般都是 ASCII 码编码后的字符,所以一般设置数据位都是 8,方便刚好一帧发送 1 个字节。 STM32串口通讯详解 串口通讯的物理层有很多标准及变种,主要讲解 RS-232 标准 ,RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。因为我们常见的市面上的开发板在串口通讯那一讲都是关于RS-232标准的协议。 我们这里就不讲啥结构体了,这些直接去看数据手册就好了,讲一下配置过程步骤,做到胸有成竹、心中有数。 1.使能串口引脚GPIOA的时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); 2.使能串口的时钟,串口挂载在AHB2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); 3.将串口的引脚复用到串口中断线上 /*连接 PA10 复用到 USART1_Rx*/ GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); /*连接 PA9 复用到 USART1__Tx*/ GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); STM32有很多的内置外设,这些外设的外部引脚都是与GPIO复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPIO作为内置外设使用的时候,就叫做复用。例如串口1的发送接收引脚是PA9,PA10,当我们把PA9,PA10不用作GPIO,而用做复用功能串口1的发送接收引脚的时候,叫端口复用。 4.常规操作初始化串口引脚的GPIO /* GPIO初始化 */ GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* 配置Tx引脚为复用功能 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 配置Rx引脚为复用功能 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA, &GPIO_InitStructure); 5.串口参数初始化 串口初始化是通过 USART_Init()函数实现的。 /* 波特率设置:115200 */ USART_InitStructure.USART_BaudRate = 115200; /* 字长(数据位+校验位):8 */ USART_InitStructure.USART_WordLength = USART_WordLength_8b; /* 停止位:1个停止位 */ USART_InitStructure.USART_StopBits = USART_StopBits_1; /* 校验位选择:偶校验 */ USART_InitStructure.USART_Parity = USART_Parity_No; /* 硬件流控制:不使用硬件流 */ USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; /* USART模式控制:同时使能接收和发送 */ USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; /* 完成USART初始化配置 */ USART_Init(USART1, &USART_InitStructure); /* 使能串口 */ USART_Cmd(USART1, ENABLE); /*开启中断 接收到数据产生中断 进入中断服务函数 */ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); 初始化需要设置的参数为:波特率,字长,停止位,奇偶校验位,硬件数据流控制,模式(收,发)。 这里面的USART_Cmd();函数很好理解,就是是能串口。USART_ITConfig();就是开启中断响应了,这里面的第二个入口参数我们一般写的是USART_IT_RXNE.也就是打开接收中断,即程序在发送数据结束的时候要产生中断,调到中断服务函数中。 6.设置串口中断优先级分组 我们前一章说了,只要你的程序里面用了中断,就必须配置串口优先级分组。但是我们我们有时候会发现我们在一些厂家的串口例程中没有配置串口中断优先级分组,这是因为他程序只有一个串口中断就没有所谓的优先级,配不配置,程序都只有个中断。 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); 7.编写串口中断服务函数 这个函数很重要,既然有中断就要执行串口中断服务函数中去,执行相应的指令。 void USART1_IRQHandler(void) { if(USART_GetITStatus( USART1, USART_IT_RXNE ) != RESET)//获取接收中断标志位(接收到的数据必须是0x0d 0x0a结尾) { Res = USART_ReceiveData( USART1 ); if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>( USART_Rec_Len-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } } } } } 7.1 中断服务函数名不是随便起的,USART1_IRQHandler表示串口一个中断服务函数,函数名字在启动文件startup_stm32f10x_hd.s文件中可以找到。 7.2 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 见名知意,得到串口中断的标志位,判断是否发生串口1接收中断,如果是串口接收中断,则读取串口接受到的数据。 7.3 Res =USART_ReceiveData(USART1);将从串口1读取接收到的数据赋值给变量Res。 7.4 简单的 接 收 协 议。通 if语句 , 配 合 一 个 数 组USART_RX_BUF[],一个接收状态寄存器 USART_RX_STA(此寄存器其实就是一个全局变量,由读者自行添加。由于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对串口数据的接收管理。USART_RX_BUF 的大小由 USART_Rec_Len 定义,也就是一次接收的数据最大不能超过 USART_Rec_Len 个字节。USART_RX_STA 是一个接收状态寄存器。 接收到从电脑串口调试助手发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2 个字节组成:回车符的ASCII码是0X0D 和换行符的ASCII码是 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_Rec_Len 的时候,则会丢弃前面的数据,重新接收。 8.编写自定义发送函数 这个自定义的发送函数是我们直接在程序中发送数据到串口调试助手,这个自定义的函数和中断服务函数无关,因为中断服务函数是用来接收数据的,就是我们通过串口调试助向单片机发送数据,需要用到中断服务函数。换句话说,如果你只想通过单片机将数据发送到串口调试助手的话,就不需要写中断服务函数了。但是这样做法没有意义,我们用串口是主要是接收数据的,单单发送一个数据意义不大。 /***************** 发送一个字符 **********************/ static void Uart_SendByte(uint8_t ch) { /* 发送一个字节数据到USART1 */ USART_SendData(USART1,ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //获取发送状态 } /***************** 发送字符串 **********************/ void Uart_SendString(uint8_t *str) { uint8_t k=0; do { Uart_SendByte(*(str + k) ); k++; } while(*(str + k)!=' |