STM32F103ZET6+USART+串口通信
一、 通信概述
通信就是数据交换、信息交换。嵌入式系统中,微控制器与其他外围设备相互连接,系统各部件之间进行数字信号/数据的传输就是通信,无论是大型嵌入式系统还是小型嵌入式系统,都需要用到通信。
通信的方式有很多种,按数据传送方式可分为串行通信和并行通信;按通信数据同步方式可分为同步通信和异步通信;在串行通信中按数据传输方向及与时间的关系可分为单工通信、半双工通信和全双工通信。
二、 串行通信与并行通信
串行通信(Serial Transmission),是指通过一根数据线或少量数据线(少于8根)将数据一位一位地按顺序依次传送。
并行通信(Parallel Transmission),是指用多条数据线同时传送多位数据,通常以8位、16位、32位等数据位传送。并行通信一次可传输多个数据位的数据,并行通信传输数据量是串行传输的数倍,如早期的并行接口的硬盘、打印机,采用PCI(Peripheral Component Interconnect,外设部件互连标准)并行接口的显卡等。
并行传输的缺点:长距离传输成本较高;信号线之间的干扰大,传输速率很难达到很高;体积大,占用空间大,不利于设备的小型化。
三、 同步通信与异步通信
同步通信是连续串行传送数据的通信方式,要求收发双方的时钟必须保持严格的同步。特点:输速率较高
异步通信在发送的有效数据中增加一些用于同步的控制位,比如开始位和停止位等,数据以字符为单位组成数据帧进行传送,收发双方需约定数据的传输速率。特点:传输效率较低;通信设备实现简单、成本低。
四、单工、半双工、全双工通信
根据数据传输方向以及与时间的关系,串行通信可以分为单工、半双工和全双工三种通信方式。
波特率:每秒传输的二进制位数,单位为比特每秒(bit/s,bps),是衡量串行数据传输速度快慢的指标。
字符速率:每秒所传输的字符数:波特率=字符速率×每个字符包含的位数
五、 异步串行通信协议
异步串行通信标准的数据帧由起始位、数据位、校验位、停止位四部分组成。数据传输速率为50、75、100、150、300、600、1200、2400、4800、9600、19200和38400波特。
起始位:占一位,位于数据帧的开头,以逻辑“0”表示传输数据的开始。
数据位:要发送的数据,数据长度可以是5~8位。
校验位:占一位,用于检测数据是否有效。
停止位:一帧传送结束的标志,根据实际情况定,可以是1、1.5或2位。
空闲位:数据传输完毕,用“1”表示当前线路上没有数据传输。
UART(Universal Asynchronous Receiver Transmitter,通用异步收发传输器)是一个全双工通用异步串行收/发模块,主要用于打印程序调试信息、上位机和下位机的通信以及ISP程序下载等场合。
UART至少需要两根数据线用于通信双方进行数据双向同时传输,最简单的UART接口由TxD、RxD、GND共3根线组成。其中,TxD用于发送数据,RxD用于接收数据,GND为信号地线,通过交叉连接实现两个芯片间的串口通信。
六、STM32 USART编程模式
1、轮询模式
CPU不断地查询I/O设备是否准备就绪,如果准备就绪就发送,否则提示超时错误;会占用CPU的大量时间,效率低。
2、 中断方式
通过中断请求线,在I/O设备准备就绪时向CPU发出中断请求,CPU中止正在进行的工作转向处理I/O设备的中断事件;中断方式相比轮询方式效率较高。
3、DMA方式
直接存储器传送,不经过CPU直接在内存和外设之间进行批量数据交换,适用于高速大批量成组数据的传输;满足高速I/O设备的传输要求,有利于提高CPU的利用率。
USART串口应用编程步骤
1、声明GPIO和USART初始化结构体
USART_InitTypeDef USART_InitStructure;
2、串口所用的GPIO时钟使能,串口时钟使能
开启外设时钟RCC_APB2PeriphClockCmd()。例如:使能USART1、GPIOA的时钟,所用的函数为
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
3、 设置I/O引脚功能为复用推挽输出、浮空输入
串口使用的是I/O的复用功能。USART1的发送引脚为PA9,需将PA9配置为复用推挽输出;USART1的输入引脚为PA10,需将PA10配置为浮空输入。
//USART1_TX,PA9,配置为复用推挽输出,并初始化PA9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX,PA10,配置为浮空输入,并初始化PA10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
4、 设置波特率,设置数据格式:数据位、停止位、校验位
USART_InitStructure.USART_BaudRate = 115200; //串口波特率
USART_InitStructure.USART_WordLength= USART_WordLength_8b; //设置数据位占8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //设置停止位占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; //设置为收发模式
5、使用串口初始化函数USART_Init()初始化相应串口
USART_Init(USART1, &USART_InitStructure);//初始化串口1
6、利用串口使能函数USART_Cmd()使能相应串口
USART_Cmd(USART1, ENABLE);//使能串口
7、应用程序编写
若使用中断,则编写串口中断函数void USART1_IRQHandler(void)
接下来,设计通过串口1利用查询方式实现发送字符命令"Y"点亮LED灯,发送字符命令"N"熄灭LED灯。并且重定向printf()函数和scanf()函数,即重写fput()函数和fget()函数
采用查询方式进行数据通信
在之前的LED工程上,新建两个文件,一个是myusart.h文件,另一个是myusart.c文件
myusart.h文件的代码如下:
#ifndef __MYUSART_H
#define __MYUSART_H
#include "stm32f10x.h"
#include <stdio.h>
void USART_Init_Config(void);
#endif
myusart.c文件的代码如下:
#include "myusart.h"
void USART_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //开始USSART1和GPIOA的时钟
USART_DeInit(USART1); //¸复位USART1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //选择要使用的I/O引脚,此处选择PA9引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚的输出速度为50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚输出模式为复用推挽输出模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //选择要使用的I/O引脚,此处选择PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置引脚输入模式为浮空输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1 配置
USART_InitStructure.USART_BaudRate = 115200; //设置波特率为115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位占8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //设置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_Cmd(USART1, ENABLE); //使能串口1
}
//重定向printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向scanf函数
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
main.c文件的代码如下:
#include "stm32f10x.h"
#include "led.h"
#include "myusart.h"
int main(void)
{
char ch;
LED_Init();
USART_Init_Config();
while(1)
{
ch = getchar();
printf("接收到的指令º%c \n",ch);
switch(ch)
{
case 'N':
GPIO_SetBits(GPIOB,GPIO_Pin_5); //熄灭LED灯
printf("熄灭LED灯\n");
break;
case 'Y':
GPIO_ResetBits(GPIOB,GPIO_Pin_5); //点亮LED灯
printf("点亮LED灯\n");
break;
default:
printf("请输入正确的指今,N熄灭灯,Y点亮灯!\n");
break;
}
}
}
运行效果
打开串口调试助手
接下来,设计通过串口1利用中断方式实现发送字符命令"1"点亮LED灯,发送字符命令"0"熄灭LED灯。并且重定向printf()函数和scanf()函数,即重写fput()函数和fget()函数
采用中断方式进行数据通信
myusart.h文件的代码如下:
#ifndef __MYUSART_H
#define __MYUSART_H
#include "stm32f10x.h"
#include <stdio.h>
void USART_Init_Config(void);
#endif
myusart.c文件的代码如下:
#include "myusart.h"
void USART_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义一个GPIO_InitTypeDef类型的结构体变量,用于配置GPIo引脚
USART_InitTypeDef USART_InitStructure; //定义一个USART_InitTypeDer类型的结构体变量,用于配置串口
NVIC_InitTypeDef NVIC_InitStructure; //定义一个NVIC_InitTypeDef类型的结构体变量,用于配置中断优先级
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //开始USART1和GPIOA的时钟
USART_DeInit(USART1); //复位USART1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //选择要使用的I/O引脚,此处选择PA9引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚的输出速度为50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚输出模式为复用推挽输出模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //选择要使用的I/O引脚,此处选择PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置引脚输入模式为浮空输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1 配置
USART_InitStructure.USART_BaudRate = 115200; //设置波特率为115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位占8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //设置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_ClearFlag(USART1,USART_FLAG_TC); //清除发送完成标准
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //打开串口1的接收中断
USART_ITConfig(USART1,USART_IT_TC,ENABLE); //开始串口1的发送中断
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
// NVIC USART1 中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择中断优先级分组2
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //外部USART1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //设置响应优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道
NVIC_Init(&NVIC_InitStructure);
}
//重定向printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向scanf函数
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
//中断处理函数
void USART1_IRQHandler(void)
{
uint8_t Rx_Data;
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
{
Rx_Data = (uint8_t)USART_ReceiveData(USART1);
printf("输出的命令是 %c \n",Rx_Data);
switch(Rx_Data)
{
case '0':
GPIO_SetBits(GPIOB,GPIO_Pin_5);
printf("熄灭LED灯\n");
break;
case '1':
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
printf("点亮LED灯 \n");
break;
default:
printf("输入了错误的指令,请重新输入! \n");
break;
}
}
}
main.c文件的代码如下:
#include "stm32f10x.h"
#include "led.h"
#include "myusart.h"
int main(void)
{
LED_Init();
USART_Init_Config();
while(1)
{
}
}
运行效果
打开串口调试助手
接下来,设计通过串口1利用DMA方式实现循环发送字符。
采用DNA方式进行数据通信
myusart.h文件的代码如下:
#ifndef __MYUSART_H
#define __MYUSART_H
#include "stm32f10x.h"
#include <stdio.h>
#define SENDBUFF_SIZE 100
void USARTx_Init_Config(void);
void USARTx_DMA_Config(void);
#endif
myusart.c文件的代码如下:
#include "myusart.h"
uint8_t SendBuff[SENDBUFF_SIZE];
void USARTx_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //开启USART1和GPIOA的时钟
USART_DeInit(USART1); //¸复位USART1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //选择要使用的I/O引脚,此处选择PA9引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚的输出速度为50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚输出模式为复用推挽输出模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //选择要使用的I/O引脚,此处选择PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置引脚输入模式为浮空输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1
USART_InitStructure.USART_BaudRate = 115200; //设置波特率为115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位占8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //设置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_Cmd(USART1, ENABLE); //使能串口1
USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_TXE|USART_FLAG_RXNE); //清除发送完成标志
}
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* 开启DNA时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*设置DNA源:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)(&(USART1->DR));
/*内存地址(要传输的变量指针)*/
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
/*方向:从内存到外设*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
/*外设地址不增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/*内存地址自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*外设数据单位*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
/*内存数据单位 8bit*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/*DMA模式:不断循环*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*优先级:中*/
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/*禁止内存到内存的传输*/
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
/*配置DMA1的4通道*/
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
/*使能DMA*/
DMA_Cmd (DMA1_Channel4,ENABLE);
/* 配置DMA发送完成后产生中断 */
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);
}
//重定向printf函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向scanf函数
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
main.c文件的代码如下:
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "myusart.h"
extern uint8_t SendBuff[SENDBUFF_SIZE];
int main(void)
{
uint16_t i;
delay_init();
LED_Init(); //LED初始化
USARTx_Init_Config(); //USART1初始化
USARTx_DMA_Config(); //DMA初始化
printf("使用DMA方式传输串口数据\n");
/*输入要发送的数据,这里选用A作用A作为DMA传送的数据源*/
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'A';
}
/* USART1 向DMA发出TX请求 */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_5);
delay_ms(1000);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
delay_ms(1000);
}
}
运行效果
打开串口调试助手