基于STM32模拟UART串口通信
cathy 在 提交
cathy 在 提交
什么是串口通讯?
串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式。尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输。
典型的串口通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,所以端口能够在一根线上发送数据同时在另一根线上接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶的校验。对于两个需要进行串口通信的端口,这些参数必须匹配,这也是能够实现串口通讯的前提。如下是串行通讯示数据传输意图。
串口通讯的通讯协议
最初数据是模拟信号输出简单过程量,后来仪表接口出现了RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,这就促生了RS485。
我们知道串口通信的数据传输都是0和1,在单总线、I2C、UART中都是通过一根线的高低电平来判断逻辑1或者逻辑0,但这种信号线的GND再与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。所以差分通信、支持多机通信、抗干扰强的RS485就被广泛的使用了。
RS485通信最大特点就是传输速度可以达到10Mb/s以上,传输距离可以达到3000米左右。大家需要注意的是虽然485最大速度和最大传输距离都很大,但是传输的速度是会随距离的增加而变慢的,所以两者是不可以兼得的。
串口通讯的物理层
串口通讯的物理层有很多标准,例如上面提到的,我们主要讲解RS-232标准,RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。
在上面的通讯方式中,两个通讯设备的"DB9接口"之间通过串口信号线建立起连接,串口信号线中使用"RS-232标准"传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的"TTL校准"的电平信号,才能实现通讯。
下图为DB9标准串口通讯接口:
DB9引脚说明:
上表中的是计算机端的DB9公头标准接法,由于两个通讯设备之间的收发信号(RXD与TXD)应交叉相连,所以调制调解器端的DB9母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用"直通型"的串口线连接起来即可。
串口线中的RTS、CTS、DSR、DTR及DCD信号,使用逻辑 1表示信号有效,逻辑0表示信号无效。例如,当计算机端控制DTR信号线表示为逻辑1时,它是为了告知远端的调制调解器,本机已准备好接收数据,0则表示还没准备就绪。
波特率
波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示。
比如波特率为9600bps;代表的就是每秒中传输9600bit,也就是相当于每一秒中划分成了9600等份。
因此,那么每1bit的时间就是1/9600秒=104.1666...us。约0.1ms。既然是9600等份,即每1bit紧接着下一个比特,不存在额外的间隔。两台设备要想实现串口通讯,这收发端设置的波特率必须相同,否则是没办法实现通讯的:动图演示常用通信协议原理。
收发波特率一致可以实现通讯:
收发波特率不一致,导致RX端不能正常接收:
串口通讯的数据结构
起始位:起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。
数据位:数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定。传输数据时先传送字符的低位,后传送字符的高位。
奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。
停止位:停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是逻辑1电平,标志着传输一个字符的结束。
空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。
单双工通讯
单工: 数据传输只支持数据在一个方向上传输;
半双工: 允许数据在两个方向上传输,但某一时刻只允许数据在一个方向上传输,实际上是一种切换方向的单工通信,不需要独立的接收端和发送端,两者可合并为一个端口;
全双工: 允许数据同时在两个方向上传输,因此全双工通信是两个单工方式的结合,需要独立的接收端和发送端。
STM32中的串口通讯
STM32串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器),对于大容量STM32F10x系列芯片,分别由3个USART和两个UART。
TXD:数据发送引脚;
RXD:数据输入引脚
对于两芯片的间的连接,两个芯片GND共地,同时TXD和RXD交叉连接,这样两个芯片间可进行TTL电平通信。
但如果对于芯片和PC机相连,除了共地条件外,不能使用如上的直接交叉连接,虽然两者都有TXD和RXD引脚,但通常PC机使用的是RS232接口(9针),通常是TXC和RXD经过电平转换得到,故如果要使芯片与PC机的RS232接口直接通信,需要将芯片的输入输出端口也电平转换为RS232类型,再交叉连接。
二者的电平标准不同:
单片机的点评标准(TTL电平):+5V表示1,0V表示0;
RS232电平标准:+15/+13V表示0,-15/-13表示1。
因此单片机与PC机进行串口通信应该遵循:在单片机串口与上位机给出的RS232口之间,通过电平转换电路实现TTL电平与RS232电平间的转换。如果使用USB转串口也可以实现串口通讯,USB转串口电路图如下所示。
STM32中串口通讯已经给大家建好了相应的库函数,大家在使用和配置串口的时候直接进行调用库函数和配置就行了
请大家参照一下代码:
1、初始化结构体代码
typedef struct { uint32_t USART_BaudRate; // 波特率 uint16_t USART_WordLength; // 字长 uint16_t USART_StopBits; // 停止位 uint16_t USART_Parity; // 校验位 uint16_t USART_Mode; // USART 模式 uint16_t USART_HardwareFlowControl; // 硬件流控制 } USART_InitTypeDef;
2、NVIC配置中断优先级
NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* 嵌套向量中断控制器组选择 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* 配置USART为中断源 */ NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; /* 抢断优先级*/ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /* 子优先级 */ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /* 使能中断 */ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 初始化配置NVIC */ NVIC_Init(&NVIC_InitStructure); }
3、USART配置函数
void DEBUG_USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; /* 第一步:初始化GPIO */ // 打开串口GPIO的时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); /* 第二步:配置串口的初始化结构体 */ // 打开串口外设的时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 配置串口的工作参数 // 配置波特率 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置 针数据字长 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置停止位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置校验位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置硬件流控制 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置工作模式,收发一起 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 完成串口的初始化配置 USART_Init(DEBUG_USARTx, &USART_InitStructure); /*--------------------------------------------------------*/ // 串口中断优先级配置 NVIC_Configuration(); // 使能串口接收中断 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); /*--------------------------------------------------------*/ /* 第三步:使能串口 */ // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); }
来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,大部分电子设备都支持该通讯方式。串口在CKS32上应用最多的莫过于“打印”程序信息,一般在硬件设计时都会预留一个串口连接电脑,用于在调试程序时可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、指出程序运行出错位置等等。
CKS32F4xx系列产品串口介绍
CKS32F4xx系列最多可提供6路串口,其中四个USART和两个UART。USART和UART在引脚上的区别是:UART只有RX和TX引脚,而USART除了这两个引脚之外,还有流控引脚RTS和CTS,以及时钟引脚SCLK。CKS32F4xx系列产品的USART1和USART6时钟来源于APB2总线时钟,其最大频率为84MHz,因此这两个串口的通信速度最高可达10.5Mbit/s。而其它四个的时钟来源于APB1总线时钟,其最大频率为42MHz,因此这四个串口的通信速度最高可达5.25Mbit/s。因为USART有SCLK引脚,因此CKS32F4xx系列产品的USART具有同步通信功能,而UART只有异步通信功能。同时USART还支持ISO7816的智能卡接口。但是当USART和UART都用在异步通信的时候,两者是没有什么区别的。CKS32F4xx系列的6个串口都支持DMA传输。
CKS32F4xx系列产品的串口在发送数据时,当发送使能位TE置1之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),然后就可以往USART_DR寄存器写入要发送的数据。在写入最后一个数据后,需要等USART状态寄存器(USART_SR)的TC位为1,表示数据传输完成,如果USART_CR1寄存器的TCIE位置1,将产生中断。串口发送的一个字符帧由三个部分组成:起始位+数据帧+停止位。起始位是一个位周期的低电平;数据帧就是我们要发送的8位或9位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。停止位时间长短是可以通过USART控制寄存器2(USART_CR2)的STOP[1:0]位控制,可选0.5个、1个、1.5个和2个停止位。默认使用1个停止位。2个停止位适用于正常USART模式、单线模式和调制解调器模式。0.5个和1.5个停止位用于智能卡模式。
CKS32F4xx系列产品的串口在接收数据时,需要先将USART_CR1寄存器的RE 位置1,使能USART接收,使得接收器在RX线开始搜索起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果 USART_CR2寄存器的RXNEIE置1的话可以产生中断。
CKS32F4xx系列产品控制器的USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数据帧加上1位的校验位总共9位,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,则可以产生奇偶校验中断。使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧 +校验位+停止位。
USART有多个中断请求事件,具体如下表所示:在串口的中断服务函数里,通过对这些中断事件标志的检测,就可以判断出是何种事件发生,然后再做出相应的处理。
CKS32F4xx系列产品串口的配置
接下来我们讲解如何利用CKS32F4xx系列固件库来完成对串口的配置使用。首先标准库函数定义了一个串口初始化结构体USART_InitTypeDef,结构体成员用于设置串口的工作参数,并由外设初始化配置函数USART_Init()调用,从而完成对串口相应寄存器的配置,进一步达到完成对串口配置的目的。
typedef struct { uint32_t USART_BaudRate; // 波特率 uint16_t USART_WordLength; // 字长 uint16_t USART_StopBits; // 停止位 uint16_t USART_Parity; // 校验位 uint16_t USART_Mode; // USART 模式 uint16_t USART_HardwareFlowControl; // 硬件流控制 } USART_InitTypeDef;
结构体中各个成员变量的介绍及初始化时可被赋的值如下:
1) USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值,并设置USART_BRR寄存器值。
2) USART_WordLength:数据帧字长,可选8位或9位。它设定USART_CR1 寄存器的M位的值。如果没有使能奇偶校验控制,一般使用8位数据帧长;如果使能了奇偶校验则一般设置为9位数据帧长。
#define USART_WordLength_8b ((uint16_t)0x0000) #define USART_WordLength_9b ((uint16_t)0x1000)
3) USART_StopBits: 停止位设置,可选0.5个、1个、1.5个和 2个停止位,它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。
#define USART_StopBits_1 ((uint16_t)0x0000) #define USART_StopBits_0_5 ((uint16_t)0x1000) #define USART_StopBits_2 ((uint16_t)0x2000) #define USART_StopBits_1_5 ((uint16_t)0x3000)
4) USART_Parity: 奇偶校验控制选择,可选USART_Parity_No(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定 USART_CR1寄存器的PCE位和PS位的值。
#define USART_Parity_No ((uint16_t)0x0000) #define USART_Parity_Even ((uint16_t)0x0400) #define USART_Parity_Odd ((uint16_t)0x0600)
5) USART_Mode: USART模式选择,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。
#define USART_Mode_Rx ((uint16_t)0x0004) #define USART_Mode_Tx ((uint16_t)0x0008)
6) USART_HardwareFlowControl: 硬件流控制选择,只有在硬件流控制模式才有效,可选使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。
#define USART_HardwareFlowControl_None ((uint16_t)0x0000) #define USART_HardwareFlowControl_RTS ((uint16_t)0x0100) #define USART_HardwareFlowControl_CTS ((uint16_t)0x0200) #define USART_HardwareFlowControl_RTS_CTS ((uint16_t)0x0300)
当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置,不使用时不需要设置。
typedef struct { uint16_t USART_Clock; // 时钟使能控制 uint16_t USART_CPOL; // 时钟极性 uint16_t USART_CPHA; // 时钟相位 uint16_t USART_LastBit; // 最尾位时钟脉冲 } USART_ClockInitTypeDef;
结构体中各个成员变量的介绍及初始化时可被赋的值如下:
1) USART_Clock: 同步模式下SCLK引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2寄存器的CLKEN位的值。
#define USART_Clock_Disable ((uint16_t)0x0000) #define USART_Clock_Enable ((uint16_t)0x0800)
2) USART_CPOL: 同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定USART_CR2寄存器的CPOL位的值。
#define USART_CPOL_Low ((uint16_t)0x0000) #define USART_CPOL_High ((uint16_t)0x0400)
3) USART_CPHA: 同步模式下SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。
#define USART_CPHA_1Edge ((uint16_t)0x0000) #define USART_CPHA_2Edge ((uint16_t)0x0200)
4) USART_LastBit: 选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK引脚输出,可以是不输出脉冲(USART_LastBit_Disable)、输出脉冲 (USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。
#define USART_LastBit_Disable ((uint16_t)0x0000) #define USART_LastBit_Enable ((uint16_t)0x0100)
要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。
(1) void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)函数:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) { assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_USART_DATA(Data)); USARTx->DR = (Data & (uint16_t)0x01FF); }
该函数的功能是向串口寄存器USART_DR写入一个数据,有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是待发送的数据,其值只要满足如下条件即可:
#define IS_USART_DATA(DATA) ((DATA) <= 0x1FF)
(2) uint16_t USART_ReceiveData(USART_TypeDef* USARTx)函数:
uint16_t USART_ReceiveData(USART_TypeDef* USARTx) { assert_param(IS_USART_ALL_PERIPH(USARTx)); return (uint16_t)(USARTx->DR & (uint16_t)0x01FF); }
该函数的功能是从USART_DR寄存器读取串口接收到的数据,只有一个入口参数,即选择是哪个串口。
(3) void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)函数:
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState) { assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_FUNCTIONAL_STATE(NewState)); if (NewState != DISABLE) { USARTx->CR1 |= USART_CR1_UE; } else { USARTx->CR1 &= (uint16_t)~((uint16_t)USART_CR1_UE); } }
要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。
该函数的功能是使能串口。有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是使能或者不使能,其值为DISABLE或者ENABLE。
(4) void USART3_IRQHandler(void) 串口中断服务程序函数:
当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写我们相应的逻辑代码即可。
(5) FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)函数:
该函数的功能是读取串口的状态,第一个入口参数和上面的一样。这里重点讲解第二个入口参数,它是标示我们要查看串口的哪种状态,可选的值及其代表的意义如表格所示:
(6) ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)函数:
当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。经常我们在中断处理函数中,要判断该中断是哪种中断,这时候就会使用该函数。第二个入口参数可选的值及其代表的意义如表格所示:
串口接发通信实验
接下来我们根据上面讲解的串口通信的知识,实际编写一个软件程序实现串口的接发通信。代码实现的现象是在开发板一上电时会通过print函数发送一串字符串“start”给电脑,然后开发板进入中断接收等待状态。如果电脑有发送数据过来,开发板就会产生中断, 我们在中断服务函数里接收数据,并将接收到数据标志位置1,在主函数里对标志位经过判断之后再把数据返回发送给电脑。
1.编程要点
1) 使能RX和TX引脚GPIO时钟和USART3时钟;
2) 初始化GPIO,并将GPIO复用到USART3上;
3) 配置USART3参数;
4) 配置中断控制器并使能USART3接收中断;
5) 使能USART3;
6) 在USART3接收中断服务函数里接收数据并将接收到数据的标志位置1。
2.代码分析
代码清单1:USART3初始化配置
其初始化串口的过程和我们前面讲解的编程要点中的过程是一致的。因为我们使用到了串口的中断接收,因此需要开启串口3的NVIC中断并对其进行配置。
void uart_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; //使能RX和TX引脚GPIO时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能USART3时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //初始化GPIO,并将GPIO复用到USART3上 GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3); GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB,&GPIO_InitStructure); //配置USART3参数 USART_InitStructure.USART_BaudRate = bound; //波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_Init(USART3, &USART_InitStructure); //使能USART3 USART_Cmd(USART3, ENABLE); //配置中断控制器并使能USART3接收中断; #if EN_USART3_RX USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif }
代码清单2:USART3中断服务函数
当USART3有接收到数据时就会执行USART3_IRQHandler函数。然使用if 语句来判断是否是真的产生USART3数据接收这个中断事件,如果是真的就使用 USART数据读取函数USART_ReceiveData读取数据到指定存储区Res,并将自己定义的一个标志位Rxflag置1。
void USART3_IRQHandler(void) { if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { Res =USART_ReceiveData(USART3);//(USART1->DR); Rxflag=1; } }
代码清单3:字符函数
//发送一个字符函数 static void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch ) { USART_SendData(pUSARTx,ch); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } //发送指定长度字符的函数 void Usart_SendStr_length( USART_TypeDef * pUSARTx, uint8_t *str,uint32_t strlen ) { unsigned int k=0; do { Usart_SendByte( pUSARTx, *(str + k) ); k++; } while(k < strlen); }
Usart_SendByte函数用来在指定USART发送一个ASCLL码值字符,它有两个形参,第一个为USART,第二个为待发送的字符。它是通过调用库函数 USART_SendData来实现的,并且增加了等待发送完成功能。
Usart_SendString函数用来发送一个字符串,它实际是调用 Usart_SendByte函数发送每个字符,直到遇到空字符才停止发送。最后使用循环检测发送完成的事件标志来实现保证数据发送完成后才退出函数。
代码清单4:printf函数支持
#if 1 #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x) { x = x; } int fputc(int ch, FILE *f) { while((USART3->SR&0X40)==0);//循环发送,直到发送完毕 USART3->DR = (u8) ch; return ch; } #endif
这段代码是引入printf函数支持所必须的,加入这段代码加入之后便可以通过printf函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值。如果我们使用不同的串口,对这段代码的修改一般也只是用来改变 printf 函数针对的串口号,比如将上述代码中的USART3改成USART1即可。
代码清单5:主函数
u8 Res;u8 Rxflag; u8 USART_RX_BUF[USART_REC_LEN]; u8 usRxCount=0; int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(168); uart_init(115200); printf("start\r\n"); while(1) { if(Rxflag) { if (usRxCount < sizeof(USART_RX_BUF)) { USART_RX_BUF[usRxCount++] = Res; } else { usRxCount = 0; } /* 遇到换行字符,就把数据发送到串口助手*/ if (Res == 0x0A) /* 换行字符 */ { Usart_SendStr_length(USART3,USART_RX_BUF, usRxCount ); usRxCount = 0; } Rxflag=0; } } }
首先我们调用NVIC_PriorityGroupConfig函数完成对NVIC的初始化,然后调用uart_init函数完成对串口的初始化,这里将串口波特率设置成可115200(位/秒)。接着利用printf函数发送一次“start”到串口调试助手。然后对Rxflag的值进行判断,当接收到了数据,即Rxflag的值为1时,对接收的数据长度进行判断,USART_REC_LEN是我们定义的接收最大字节数,这个值可以根据自己的需要进行修改。当接收的数据在最大字节数范围之内时,把接收到的数据赋值到数组USART_RX_BUF里,同时当接收到的数据为0x0A,即换行字符时,利用Usart_SendStr_length函数将接收到的数据发送出去。因此在利用串口调试助手向MCU发送数据时,要勾选“加回车换行符”。
在本程序中我们设置串口进入中断的方式为数据寄存器非空即进一次中断,因此每个字节的接收都会进一次中断,这会导致CPU的效率大大降低,因此在下一节我们将会讲解利用DMA的方式对串口的数据进行发送和接收。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
cathy 在 提交
来源:公众号【鱼鹰谈单片机】
作者:鱼鹰Osprey
ID :emOsprey
工作过程中,总是会遇到各种各样的通信问题,除了掌握软件知识,必要的硬件技能也必不可少,比如万用表、示波器、逻辑分析仪等,如此才能做到精准定位,早点打卡下班~~
鱼鹰根据个人多年的嵌入式开发经验,在此斗胆总结一番,希望可以给一些新人提供排查方向。
在此,以串口通信为例,介绍排查步骤或方向:
1、示波器看波形
通信,不管是单向通信还是双向通信,必然存在两个器件,所以我们需要重点关注这两个,而两者之间必然存在物理连接--导线(无线除外),遇到通信问题,应该首先保证导线连接正常、电压正常,如果这一点都没有确定,直接跳过该步骤,很大可能做无用功,查来查去,最终可能查了一个寂寞。
串口双向通信,一般会设计成主从方式,即一个主器件通过双方约定好的协议主动向从机发起数据传输,并且从机永远是被动应答。这样保证在多从机通信的情况下,不会出现数据错乱的情况(如果多个从机同时发送数据,可能造成交通拥堵,毕竟一个方向只有一条道)。
这种情况下,可以让主机定时发送固定数据帧(比如版本查询,这样可以减少变量),通过查看示波器来确定从机是否有返回数据。
如此,我们可以确定两个问题:
1、观察主机发送引脚波形是否正常(串口平时一般为高电平,发数据时才会变化)、同时需要确定电压是否正常、波特率可看可不看、具体传输数据也是,因为该阶段只是从大的方向进行排查;
2、从机是否有回应。
这里又分为两种情况:
1)从机没有回应:
此时我们需要在接下来的排查步骤中确定几个问题:
<1>从机发送功能是否正常?
<2>主机发送的数据从机是否已经正常接收?
<3>主机发送的数据协议是否正确。
2)从机有回应。
这种情况下,问题就比较简单,重点排查上层协议即可(可以保证从机的收、发没有问题)。
这里特别注意的是,波形测量位置一定是在最终点,而不是中间某个探测点或者模拟开关之类的器件。
比如,主机发送引脚,测量位置应该在从机芯片的接收引脚,而测量主机的接收则在主机芯片的接收引脚,才不会导致结果误判。
2、根据波形情况,确定主从器件发送、接收功能
如果说步骤 1 发现主机没有正常传输波形产生,就要根据情况再确定一些问题。
1、如果发现波形失真、变形、电压不正常等情况,请呼叫硬件工程师一起排查。
2、主机芯片发送引脚可以看到波形,但从机接收引脚看不到,请使用万用表确定是否虚焊接。相反方向也排查一遍。
3、主机或从机不能正常发送或接收。
如果你使用了 DMA 发送,又使用了《终极串口接收方式,极致效率》笔记介绍的方式接收,那么你可以从以下方向进行排查(其他更简单的方式类似):
1)发送、接收引脚时钟是否开启、输入输出模式是否配置正常,发送引脚一般复用输出、接收一般上拉输入(如果只是引脚配置错误,发送 DMA的 计数器会变化,但是没有实际波形输出);
2)串口外设寄存器配置是否正常。如果全是 0 ,说时钟没有打开,或者未调用初始化函数,其次查看对应的 DMA 请求位(发送、接收,如果这个没有开启,DMA 计数器不会变化)、串口使能位等;波特率可以等发出波形再看,毕竟你现在连数据都没有。
3)查看 DMA 配置是否正常,外设、内存地址寄存器、计数器、使能位、对应通道的传输标志位(F4 如果传输标志不清除,无法启动下一次传输),总之把对应结构体的成员看一遍就对了,可能一个小配置错了,就导致整个传输失败。
4)如果是特殊引脚,看是否需要关闭默认功能,开启普通 GPIO 功能
5)如果是复映射功能,查看对应的映射寄存器是否配置正确,配置时,可能需要开启对应的时钟。
以上排查方法对发送、接收都适合。排查后,可以通过短接 Tx、Rx 引脚的方式确定发送、接收是否异常。如果是单方向的,可以配合串口模块测试。
终极杀招:
如果上述排查都没有发现问题,还是失败,要么换板子,要么换一个可以用的简单例程修改后测试,此时可以对比例程调试模式下的寄存器配置(寄存器配置截图后对比)。
另外, 排查时需要特别注意的是,不能只看代码,不看实际寄存器的值。因为有的时候,代码也可能有各种各样的问题(比如时钟如果没有打开,那么即使有对应的配置代码,你一个寄存器值也写不进去),只有看最终寄存器的值才最安心。这里也多次强调了时钟的重要性,不过一般这种问题很容易排查。
以上排查方向,配合鱼鹰的调试系列文章,消化好!
3、协议问题
上述排查,应该基本解决了双方通信问题的,即自发自收应该没有问题。但不代表双方就能够正常通信了。
这里可能存在几个问题:
1、双方波特率设置不匹配(这个问题比较容易查,直接看波形或寄存器(寄存器可能还没那么靠谱,外设一致可以)即可)
2、上层协议不匹配,比如 CRC 校验有问题、帧头定义有误等等……
3、对方解析函数有问题。
这个时候,配合逻辑分析仪分析是最方便的,到底是从机问题还是主机问题一目了然,当然你直接在Tx、Rx上并上两个串口模块的 Rx 去接收串口数据也是可以的~~
以上排查步骤不一定按顺序排查,但一般查看波形都是优先选择。
4、其他通信
在此继续介绍一下 SPI 通信可能出现的问题:
1、SPI 发送数据后,没有延时即立刻开始接收(此时从机可能没有反应过来,也就没有数据输出),SPI 的波形可能失真(通过示波器可以观察出来),也可能波形挺好,但接收就是有问题;
2、 CS 控制时机不对,或者也可能需要延时一段时间。
3、SPI 波特率太高,导致器件无法识别。测试时最好使用最低波特率,但也不排除太低波特率反而不行的情况,所以多试试几种。
总之,测试代码应该要考虑最极端的情况(比如延时久一点,波特率慢一点),调通之后再优化这些参数,提高通信效率。
另外,如果有参考例程,可以直接对比例程输出的波形进行修改,直到你的代码输出波形(注意,这里直接对比波形)和例程保持一致,如果还是有问题,那么换从机器件再试。当然,电源这里也一定要先确定没有问题。
一般来说,只要 CS、CLK、MOSI 控制时序正确,器件的 MISO 引脚一定会有数据输出(即使你没有接主机的 MISO),如果你能看到 MISO 输出波形,但主机还是没有正确接收,那么就查主机的 MISO 引脚配置和延时情况了。
I2C 通信:
1、和 SPI 类似,查看时序问题
2、注意地址问题,有可能说明书的地址和实际的地址之间换算可能需要移位。
3、注意从机锁死问题,传输前发几个停止信号过去试试
来源:鱼鹰谈单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
cathy 在 提交
刚开始学单片机的你,是不是会因用程序把LED点亮而感到高兴,会因用程序把数码管点亮而感到高兴。这是好事,这也是想继续学习下去的动力。
但是到了与数据相关的实验时,却感觉很难有所进步。有时候,把驱动写好了,下载到单片机后,一点反应都没有,可是又不知道问题出在哪里,数据通信又不像LED那样可以用万用表测出到底有没有电。
这是学习单片机和STM32的一道坎。又或者说,这是一条河,阻拦着你的去路的河,有一条河你会怎么办?过去的方法很多,但是笔者觉得较快的方法就是借助原有工具渡过去。过去之后你会发现河的那边是一个不一样的世界。
那这个原有的工具是什么呢?那就是"串口通信"。
串口通信介绍
串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式......这种太过理论了,看似懂了,但又不懂。还是用我笔者自己的话来说吧。
串口通信就是可以把程序在单片机或者STM32芯片中运行的结果发送到电脑的一种通信方式。如何使用串口通讯,你需要知道的几个重要的知识点:
1.波特率。(数据传输的速度,两个设备在通信时的速度要设置成一样,否则会出现乱码)。
2.硬件连接。串口通信是一种异步通信,一般都是TX->RX,RX->TX。
3.停止位。
4.奇偶校验。
5.硬件数据流。
3、4、5,这几项一般不需要改变,所以在这就不用多说了。
串口作用
串口能用来做什么事情?
学过C语言的人都知道,程序开发需要不断调试不断验证。很多的语言编程软件都有很完整的程序调试功能。使用起来很方便。而我们的开发虽然也是用C语言,但我们的程序最终运行的环境不是在我们的开平台而是在真正的硬件系统中运行。这时想要查看程序的运行过程或者结果就不像编程软件那么方便了。
想要解决这个问题,我们这时就可以借助串口通信来把我们需要知道的结果又或者是程序运行的关键步骤发送到电脑上,我们就知道程序在硬件系统中运行是否出现问题。51单片机可以用串口来调试,STM32可以用串口来调试。高级一点的LINUX开发板也是使用串口来调试。
学会串口通信能做什么
当你学会串口通信时,你可以开始玩WIFI模块、GSM模块、蓝牙模块、GPS模块、以及各种使用串口通信的传感器等等。有能力你还可以编写上位机软件通过串口通信来控制设备。
串口通信需要什么
如果你只有STM32核心板,那么你还需要一个串口转USB模块和一个串口数据接收软件还有几根杜邦线就可以了。如果你的是比较完整的一款开发板的话,一般都已经带有串口转USB模块,这样使用起来就更简单了。聪明的你是不是发现还缺少了点什么,对。没错因为还缺少了最重要的程序。想要使用串口通信当然还需要写串口通信的程序。下面就教你如何去用,而不是写。
串口实验
在做一个实验时,最好把这个实验分割成几个关键的步骤,这样做的好处就是可以清晰的知道自己需要做什么,以及做完了哪些。还有哪些还没做。下面笔者把串口实验分成几个关键的步骤:
1)串口通信使用到的GPIO引脚配置
STM32F103系列的芯片一般都有三个串口以上,用来调试使用的串口一般都是使用USART1。其他的串口配置都是一样的。
下面这段就是串口配置的程序:
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能USART1,GPIOA时钟 //USART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9 //USART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
串口使用的的GPIO口是PA9和PA10,所以只需配置这两个IO口的输入输出模式就可以了。
2)串口主要参数设置(直接看程序)
USART_InitTypeDef USART_InitStructure; //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound; //串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据格式,8位 USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接受中断
串口参数配置无法就是配置串口的波特率、数据格式、停止位、奇偶校验、硬件流、收发模式。除了波特率需要改变其他的参数都不需要管。直接复制拿来用。
3)串口中断配置
串口如果使用中断接收,那么就需要配置串口的中断参数,配置项无法就是配置那个的中断源和中断的优先级。
NVIC_InitTypeDef NVIC_InitStructure; //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;//子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能 NVIC_Init(&NVIC_InitStructure);//根据指定的参数初始化VIC寄存器
4)串口使能
就是你需要什么时候开始使用串口功能,就是一句原有的函数。
USART_Cmd(USART1, ENABLE); //使能串口1
5)编写串口中断处理函数
使用库函数开发,所有的中断函数都是已经存在的,只是中断函数里面没有处理任何事情而已。中断函数如下:
void USART1_IRQHandler(void) { //这里是编写中断处理的内容,但是一般会先判断相关的标准才算完整 }
完整的串口中断函数:
void USART1_IRQHandler(void) { int Res=0; //定义一个变量用来接收串口数据集 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1);//读取接收到的数据 USART_SendData(USART1,Res ); //把接收到的数据通过串口1发送出去 } }
串口数据的接收和发送的函数都是库函数提供的,想用时只需找到它直接拿来用就可以了。
以上就完成了一个最简单的串口实验。把程序编译烧写到STM32然后用串口转USB模块连接到电脑,使用串口数据接收软件SSCOM或其他的数据接收软件设置好波特率,打开串口,正常的话,那发送什么到STM32那就会收到什么。这样就算完成了。
这次就说到这了,如果有开发板的配套例程可以先使用,用多了,慢慢就理解了。这篇文章主要想说明串口的重要性。用串口来调试真的方便很多。
来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
首先总结一下串口232,422,485
串口232:可双向传输,全双工,最大速率20Kbps,负逻辑电平,-15V~-3V逻辑“1”, 3V~ 15V逻辑“0”。
串口422:可双向传输,4线全双工,2线单工。
串口485:可双向传输,4线全双工,2线单工,最大速率10Mb/s,差分信号,发送端:2V~ 6V逻辑“1”,-2V~-6V逻辑“0”,接收端:200mV逻辑“1”,-200mV逻辑“0”。
对于串口的实现有以两个方案:
方案一,和原子的《例说STM32》一样,首先接收,然后处理,没有消息验证处理,这样就会出现消息覆盖,消息出错后死机,无法明确区分命令,无法及时应答握手信号。
方案二,借鉴uC/OSII的消息队列,进入中断服务函数之后,关闭中断,接收数据,如果没有数据接收,等待一段时间(时间和波特率有关)后开中断,出中断,然后在对接收到的数据进行处理,下面看代码:
消息队列及其初始化函数:
这样就把数据一次性全部存储下来了,剩下的就是对消息缓冲器message_buff[]中的消息进行处理了,这样就解决了消息覆盖,消息出错无法纠正的问题,至于消息怎么处理就是依据不同的需求不同的处理,另外注意,握手信号好用定时器中断。
STM32的IO口基本操作
1.初始化结构体
先来看下GPIO_InitTypeDef这个结构体,源代码如下
结构体中包含了GPIO_Pin,GPIO_Speed和GPIO_Mode信息,GPIO_Pin在stm32f10x_gpio.h中有宏定义
刚好对应16个端口。
GPIO_Speed是一个枚举类型的结构体,在stm32f10x_gpio.h中有宏定义:
当STM32的GPIO端口设置为输出模式时,有三种速度可以选择:2MHz、10MHz和50MHz,这个速度是指I/O口驱动电路的速度,是用来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。
高频的驱动电路,噪声也高,当你不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。
当然如果你要输出较高频率的信号,但却选用了较低频率的驱动模块,你很可能会得到失真的输出信号。
关键是,GPIO的引脚速度跟应用匹配。
比如对于串口,假如最大波特率只需115.2k,那么用2M的GPIO的引脚速度就够了,既省电也噪声小。
对于I2C接口,假如使用400k波特率,若想把余量留大些,那么用2M的GPIO的引脚速度或许不够,这时可以选用10M的GPIO引脚速度。
对于SPI接口,假如使用18M或9M波特率,用10M的GPIO的引脚速度显然不够了,需要选用50M的GPIO的引脚速度。
GPIO_Mode也是一个枚举类型的结构体:
2.设置系统时钟
在使用端口前必须要开启外设时钟,在开启外设时钟前我们首先要配置好系统时钟,系统时钟的设置主要在库函数SystemInit()中完成,在启动文件startup_stm32f10x_md.s中有如下一段代码:
在调用main函数之前首先要调用SystemInit()函数,这个函数定义在system_stm32f10x.c中,函数原型为
该函数的主要功能是将配置时钟相关的寄存器都复位为默认值,并调用SetSysClock(void)函数,
对于SYSCLK_FREQ的宏定义在system_stm32f10x.c文件开头已经给出,默认的系统时钟为72M,当然前提条件是外接8M晶振。
3.开启外设时钟
开启或者关闭外设时钟主要由以下函数设置
不同的外设调用不同的函数,如果使用了io的引脚复用功能除了开启io功能时钟还需要开启复用功能时钟,例如GPIOC的Pin4还可以作为ADC1的输入引脚,当它作为ADC1来使用时,除了开启GPIOC的时钟外,还要开启ADC1的时钟
其他操作函数参看库函数使用手册。
来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
cathy 在 提交