一、了解通讯
-
串行通讯与并行通讯
串行通讯:设备之间通过少量数据信号线,地线以及控制信号线,按数据形式一位一位地传输数据。
并行通讯:设备之间通过信号线,同时传输多个数据位的数据。串行通讯的通讯距离和抗干扰能力要优于并行通讯,并且成本更低,而并行通讯的传输速率要优于串行通讯。
-
全双工,半双工和单工通讯
全双工通讯:设备之间可以同时收发数据。
半双工通讯:设备之间可以收发数据,但是不能够同时进行。
单工通讯:单方向的进行数据的发送和接收,即一个设备要么作为发送设备,要么作为接收设备。 -
同步通讯和异步通讯
同步通讯:收发双方使用同一个信号线作为时钟信号,在时钟信号的驱动下双方进行协调,同步数据。
异步通讯:不采用时钟信号进行数据同步,而是在数据信号中穿插一些同步用的信号位来实现同步。
本文章介绍的是一种常用的串行通讯方式——串口通讯
二、串口通讯的相关介绍
-
两种电平标准
TTL标准:当电平处于2.4~5V之间时,表示逻辑1;当电平处于 0 ~0.5V时,表示逻辑0。
RS-232标准:当电平处于-15~-3V之间时,表示逻辑1;当电平处于3 ~15V时,表示逻辑0。RS-232标准的传输距离及抗干扰能力更好。重点是两种标准的转换。
-
USB转串口通讯
USB转串口主要是设备跟电脑通信,该过程需要电平转换芯片来实现,常用的芯片有CH340,PL2303,CP2102,FT232。使用的时候需要安装电平转换芯片的驱动。 -
原生的串口到串口
主要是控制器跟串口设备或者传感器通信,不需要电平转换芯片来转换电平,直接使用TTL电平通信。例如GPS模块。 -
波特率与比特率
波特率即每秒钟传输的码元个数,便于对信号进行解码。常用的波特率4800,9600,115200。比特率即每秒钟传输的二进制位数。 -
通讯的起始和停止信号
起始信号由逻辑0的数据位表示,停止信号可由0.5,1.5,1或2个1的数据位来表示。双方自行约定。 -
校验
通过校验码来避免数据在传输过程中,受到外部干扰而发生偏差。常采用奇偶校验,只能检测出发生偏差位的1位。
三、USART接发通信
-
数据发送和接收的流程
数据格式
M:字长
表示数据的长度,0表示长度为8bit,1表示长度为9bit,通常设置为0。
STOP:停止位
采用2位来设置停止位的位数,00表示1个停止位,01表示0.5个停止位,10表示两个停止位,11表示1.5个停止位。
PCE:校验控制使能
用于选择是否进行硬件校验控制,0表示禁止校验控制,1表示使能校验控制。
PS:校验选择
用于选择采用奇校验还是偶校验,0表示偶校验,1表示奇校验。
PEIP:PE中断使能
由软件设置或清除,0表示禁止产生中断,1表示当USART_SR中的PE为1时,产生USART中断。
PE:校验错误
在接收模式下,若出现奇偶校验错误,硬件对该位置位,0表示没有奇偶校验错误,1表示奇偶错误。发送数据过程
数据先从PWDATA总线写入到发送数据寄存器(TDR),然后将数据一位一位的移到发送移位寄存器中,接着通过TX引脚发送出去。
接收数据过程
数据从RX引脚发到接收移位寄存器,接着将数据放到接收数据寄存器(ADR)中,最后CPU或者DMA进行读操作。 -
编写代码前的准备
①将使用已经建好的一个文件(使用固件库),将其备份成一个新文件
②在新文件中的User文件下,新建一个bsp_uart.h,bsp_uart.c,main.c文件
③使用Keil打开文件,将新建文件添加进来
添加方式
④查看相关文件
对于本文章需要查看usart的文件
查看方法
-
相关数据的介绍
①USART初始化结构体typedef struct { uint32_t USART_BaudRate;//波特率BRR uint16_t USART_WordLength;//字长CR1_M uint16_t USART_StopBits;//停止位CR2_STOP uint16_t USART_Parity;//校验控制CR1_PCE,CR1_PS uint16_t USART_Mode;//模式选择CR1_TE,CR1_RE(发送和接收) uint16_t USART_HardwareFlowControl;//硬件流选择CR3_CTSE,CR3_RTSE } USART_InitTypeDef;
在固件库中,对每个属性都进行了一些相关设置,请自行进行查看。
②同步时钟初始化结构体typedef struct { uint16_t USART_Clock;//同步时钟CR2_CLKEN(时钟使能) uint16_t USART_CPOL;//极性CR2_CPOL uint16_t USART_CPHA;//相位CR2_CPRA uint16_t USART_LastBit;//最后一个位的时钟脉冲CR2_LBC } USART_ClockInitTypeDef;
-
相关固件函数介绍
①串口初始化函数void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
②中断配置函数
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
③串口使能函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
④数据发送函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
⑤数据接收函数
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
⑥中断状态位获取函数
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
在固件库中,还定义了其他函数,可以自行查看。
-
代码编写
①一个简单的串口发送数据
bsp_uart.h
#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "stm32f10x.h"
//串口1-USART1
#define DEBUG_UARTx USART1;
#define DEBUG_UART_CLK RCC_APB2Periph_USART1
#define DEBUG_UART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_UART_BAUDRATE 115200
//USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
void DEBUG_UART_Config(void);
#endif /*_BSP_UART_H*/
bsp_uart.c
#include "./uart/bsp_uart.h"
void DEBUG_UART_Config(void)
{
/*第一步:初始化GPIO*/
//定义GPIO对象
GPIO_InitTypeDef GPIO_InitStructure;
//定义串口对象
USART_InitTypeDef USART_InitStructure;
//打开串口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);
/*第三步:使能串口*/
USART_Cmd(DEBUG_USARTx,ENABLE);
}
main.c
#include "stm32f10x.h"
#include "./uart/bsp_uart.h"
int main(void)
{
DEBUG_UART_Config();
USART_SendData(DEBUG_USARTx,0xaa);
while(1);
}
通过点击图中图标,进行编译
烧录程序的设置(本过程采用ST_Link
)
Output的设置
选择对应的下载器
此处端口选择SW
将图中内容打上勾
设置完成后,再次编译程序,就可以进行烧录程序。
烧录成功
连接USB,打开串口多功能助手,如下图
需要安装CH34驱动程序
安装过程极其简单,下载地址可以参考下面链接:
http://www.wch.cn/search?t=all&q=CH341A
安装完成后,将调试助手的内容设置与程序中的设置一致。
发送数据效果(通过点击开发板的复位键,就会显示一次数据)
②实现串口的复杂通信
要求
STM32不断的给电脑发送 “hello windows!”;只有当电脑向STM32发送 “Stop,stm32!” 后,STM32才终止向电脑发送消息。
bsp_uart.h增添如下代码
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void delay_ms(uint16_t delay_ms);
bsp_uart.c增添如下代码
//发送一个字节
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch)
{
USART_SendData(pUSARTx,ch);// 发送一个字节数据到USART
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);// 等待发送数据寄存器为空
}
//发送字符串
void USART_SendString(USART_TypeDef * pUSARTx,char *str)
{
unsigned int k=0;
do
{
Usart_SendByte(pUSARTx,*(str+k));
k++;
}while(*(str+k)!='\0');
// 等待发送完成
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}
//微秒级的延时
void delay_us(uint32_t delay_s)
{
volatile unsigned int i;
volatile unsigned int t;
for (i = 0; i < delay_s; i++)
{
t = 11;
while (t != 0)
{
t--;
}
}
}
//毫秒级的延时函数
void delay_ms(uint16_t delay_ms)
{
volatile unsigned int num;
for (num = 0; num < delay_ms; num++)
{
delay_us(1000);
}
}
//配置嵌套向量中断控制器NVIC
static void 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);
}
//并在DEBUG_USART_Config函数第三步前面添加
//串口中断优先级配置
NVIC_Configuration();
//使能串口接收中断
USART_ITConfig(DEBUG_USARTx,USART_IT_RXNE,ENABLE);
注意
:static void NVIC_Configuration(void)函数要位于void DEBUG_UART_Config(void)函数之前,否则编译会出现错误。
main.c修改成如下
#include "stm32f10x.h"
#include "./uart/bsp_usart.h"
// 接收缓冲,最大100个字节
uint8_t USART_RX_BUF[100];
// 接收状态标记位
uint16_t USART_RX_FLAG=0;
//串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t temp;
//接收中断
if(USART_GetFlagStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
{
// 读取接收的数据
temp = USART_ReceiveData(DEBUG_USARTx);
//接收未完成
if((USART_RX_FLAG & 0x8000)==0)
{
//接收到了0x0d
if(USART_RX_FLAG & 0x4000)
{
// 接收错误,重新开始
if(temp != 0x0a) USART_RX_FLAG=0;
// 接收完成
else USART_RX_FLAG |= 0x8000;
}
// 还未接收到0x0d
else
{
if(temp == 0x0d)
{
USART_RX_FLAG |= 0x4000;
}
else
{
USART_RX_BUF[USART_RX_FLAG & 0x3FFF]=temp;
USART_RX_FLAG++;
//接收数据错误,重新开始接收
if(USART_RX_FLAG > 99) USART_RX_FLAG=0;
}
}
}
}
}
int main(void)
{
uint8_t len=0;
uint8_t i=0;
// USART初始化
USART_Config();
while(1)
{
if(USART_RX_FLAG & 0x8000)
{
// 获取接收到的数据长度
len = USART_RX_FLAG & 0x3FFF;
USART_SendString(DEBUG_USARTx, "发送消息:\n");
for(i=0; i<len;i++)
{
// 向串口发送数据
USART_SendData(DEBUG_USARTx, USART_RX_BUF[i]);
//等待发送结束
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TC)!=SET);
}
USART_SendString(DEBUG_USARTx, "\n\n");
if(strcmp((char *)USART_RX_BUF,"Stop,stm32!")==0)
{
USART_SendString(DEBUG_USARTx, "stm32已停止发送!");
break;
}
USART_RX_FLAG=0;
memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));
}
else
{
USART_SendString(DEBUG_USARTx, "hello windows!\n");
delay_ms(800);
}
}
}
编译烧录程序
效果如下
分析代码
while循环
while循环的功能是让开发板不断向电脑发送 “hello windows!”的消息;当程序接收到中断(USART_IT_RXNE)时,主程序就会停下,然后执行中断服务函数,当中断服务函数执行完成后,将继续返回到主程序接收到中断的地方继续执行;此时,由于接收到上位机发送的数据,在中断服务函数对一些状态位进行改变并将接收的数据保存下来,所以程序将进入判断语句进行判断接收的数据是否为 “Stop,stm32!”,如果是,则跳出循环,终止程序;如果否,则继续循环。
中断服务函数
USART_RX_FLAG为一个16位的状态标记变量,其中,0~bit13是用于存储接收数据,根据需求选择,只要不超过这个范围就可以了;bit14用于存储是否接收到"回车\r(ASCLL:0x0d)",如果接收到则执行:将bit14置1(代码用USART_RX_FLAG |= 0x4000表示),否则不变化保持为0;如果接收了"回车\r",然后在判断下一个接收数据是否为"换行\n(ASCLL:0x0a)",如果是则执行:将bit15置1(USART_RX_FLAG |= 0x8000),表明数据接收已完成,如果有剩余的接收数据将不再存入缓存USART_RX_BUF中;如果否,则执行USART_RX_FLAG=0,表明接收错误,重新开始接收。
中断的流程
当产生一个中断的时候,主程序就会停止下来,接收到中断后,开始执行中断程序,直到中断程序执行完成后,返回到主程序接收到中断的地方,继续执行。
③使用printf来实现数据的显示
bsp_uart.c添加重定向c库函数printf
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
// 发送一个字节数据到串口
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
// 等待发送完毕
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
将main.c中使用的USART_SendString函数修改成printf输出就可以,完成数据的收发效果一致。
四、总结
整个过程并不是很复杂,只不过需要留意一些小问题。比如对于发送数据的时候,需要回车,数据才能够被正常接收,否则点击发送,不会有相应的反应。对于初学者来说,可能不是很清楚一些线的接法,可以在网上查找资料,还是很容易看懂的。