一、串口通信
(一)串口通信基本概念
1、串行/并行
通过数据传送的方式,可将通信的方式分为串行通信和并行通信两种。
2、单工/半双工/全双工
3、同步/异步通信
根据通信中的数据同步方式,又分为同步和异步两种,可以根据通信过程中是否使用时钟信号进行简单的划分。
- 同步通信:在同步通信中,收发设备双方会使用一根信号表示时钟信号,在时钟信号的驱动下,双方进行协调,同步数据。通信中通常双方会统一规定在时钟信号的上升沿或者下降沿对数据线进行采样。
- 异步通信:在异步通信中,不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些信号位用于同步信号,或者把主题数据进行打包,以数据帧的格式传输数据。同时,收发双方需要约定数据的传输速率,以便正确地解码数据。
(二)串口协议
1、UART协议(异步串行通信协议)
串口通信的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位【可选】和停止位,通信双方的数据包格式统一是正常收发数据的必要条件之一。
- 波特率:UART是串口异步通信,因此没有时钟信号,所以两个通信设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。常用的波特率为4800、9600以及115200等。这里要注意区分:比特率和波特率。
- 通信的起始符号和终止符号:串口通信的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,前提是双方要约定一致。
- 有效数据:在数据包的起始位之后紧接着就是要传输的主体数据内容,亦称为有效数据。其长度常被约定为5、6、7或8位长。
- 数据校验:在有效数据之后,有一个可选的校验位。为避免通信过程中,受外界干扰而导致数据传输出现偏差的问题,可以通过在传输过程中加上校验位来解决该问题。校验方法有:奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)和无校验(noparity),共计5种方法。
2、RS-232协议
RS232协议是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家、计算机终端生产厂家共同制定的用于串行通讯的标准,该标准规定采用一个标准的连接器,标准中对连接器的每个引脚的作用加以规定,还对信号的电平加以规定。
- 接口:该标准规定采用一个25引脚的DB-25连接器,标准中对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定;后来IBM的PC机将RS232简化成了DB-9连接器,后来成为事实标准;现在工业控制的RS-232接口一般只使用RXD、TXD、GND三条线;
- 信号:该标准规定逻辑"1"的电平为-5V到-15V,逻辑"0"的电平为+5V到+15V,选用该电气标准的目的在于提高抗干扰能力,增大通信距离,其传输距离可达15m;
3、电平标准
根据使用的电平标准不同,串口通信可分为RS-232标准及TTL标准
4、USB/TTL转232
CH340是一个USB总线的转接芯片,实现USB转串口、USB转IrDA红外或者USB转打印口。为了增加串口通讯的远距离传输及抗干扰能力,RS-232标准使用-15V表示逻辑1,+15V 表示逻辑0。常常会使用MH340芯片对USB/TTL与RS- 232电平的信号进行转换。
二、建立基于固件库的STM32工程
三、完善基于STM32固件库流水灯的工程
1、固件库之GPIO:
(1)GPIO固件库工作模式,本次实验选择的GPIO口工作模式是推挽输出:
typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入
GPIO_Mode_IPD = 0x28, // 下拉输入
GPIO_Mode_IPU = 0x48, // 上拉输入
GPIO_Mode_Out_OD = 0x14, // 开漏输出
GPIO_Mode_Out_PP = 0x10, // 推挽输出
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出
} GPIOMode_TypeDef;
—— 输入模式:上拉和下拉输入的电平由上拉或者下拉,浮空输入的电平是不确定的,完全由外部的输入决定,一般接按键的时候用的是这个模式。模拟输入则用于 ADC 采集。
——输出模式:推挽模式时双 MOS 管以轮流方式工作,输出数据寄存器 GPIOx_ODR可控制 I/O 输出高低电平。开漏模式时,只有 N-MOS 管工作,输出数据寄存器可控制 I/O输出高阻态或低电平。
(2)本次选用GPIOA——>PA0,PA1,PA2这三个IO口,如下图中所示:
(3)对这三个GPIO口进行固件库方式初始化与配置:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
//初始化GPIOA0,1,2三个端口(利用按位或选中1,2,3三个端口);GPIO_Pin_All就是初始化GPIOA全部端口(选择所有端口)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度50Mhz
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始与配置完成
2、编写工程代码:
(1)编写main.c文件:
双击打开左边之前创建并导入的main.c文件:
在main.c里面编写如下代码:
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
int main(void)
{
LED_GPIO_Config();
while (1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);//GPIOA0---PA0低电平灭
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(100);//延时0.1s
GPIO_SetBits(GPIOA,GPIO_Pin_0);//GPIOA0---PA0高电平亮
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(100);//延时0.1s
GPIO_ResetBits(GPIOA,GPIO_Pin_1);//GPIOA1---PA1低电平灭
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
Delay_ms(100);
GPIO_SetBits(GPIOA,GPIO_Pin_1);//GPIOA1---PA1高电平亮
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
Delay_ms(100);
GPIO_ResetBits(GPIOA,GPIO_Pin_2);//GPIOA2---PA2低电平灭
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(100);
GPIO_SetBits(GPIOA,GPIO_Pin_2 );//GPIOA2---PA2高电平亮
//相当于GPIO_WriteBit(GPIOA,GPIO_Pin_2,Bit_SET);
Delay_ms(100);
}
}
(2)编写延时函数:
点击新建空文档来进行编写Delay.h与Delay.c,并且保存在之前所创建的用于储存项目的文件里的User里面,去文件名字的时候,一定不要遗漏.c与.h后缀,负责就不是C文件与头文件h了:
Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);//微秒级延时
void Delay_ms(uint32_t ms);//毫秒级延时
void Delay_s(uint32_t s);//秒级延时
#endif
Delay.c
#include "stm32f10x.h"
#include "Delay.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
(3)编写GPIO初始化与配置相关函数:
led.h
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
void LED_GPIO_Config(void);
#endif
led.c
#include "LED.h"
void LED_GPIO_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
//初始化GPIOA0,1,2三个端口(利用按位或选中1,2,3三个端口);GPIO_Pin_All就是初始化GPIOA全部端口(选择所有端口)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度50Mhz
GPIO_Init(GPIOA, &GPIO_InitStructure);//
}
(4)将刚刚编写的两个C文件包含在本工程中
三、基于STM32标准库流水灯的实验
四、标准库的查询方式
(一)设置波特率
1、串口初始化
void Usart2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能串口2时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //选中串口默认输出管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //定义输出最大速率
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//定义管脚9的模式
GPIO_Init(GPIOA, &GPIO_InitStructure); //调用函数,把结构体参数输入进行初
始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口
USART_InitStructure.USART_BaudRate = 115200; //速率
115200bps
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(USART2, &USART_InitStructure); //将以上赋完值的结构体带入库函数USART_Init
进行初始化
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
USART_Cmd(USART2, ENABLE);//开启USART2,注意与上面RCC_APB2PeriphClockCmd()设置的区
别
}
2、修改波特率
void USART_Config(uint32_t baud)
{
USART_InitTypeDef USART_InitStructure;
USART_Cmd(USART2, DISABLE);
USART_InitStructure.USART_BaudRate =baud;
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(USART2, &USART_InitStructure);
USART_Cmd(USART2, ENABLE);
}
(二)通过串口发送“hello windows!
#include "sys.h"
#include "usart.h"
#include "delay.h"
int main(void)
{
u16 t; u16 len; u16 times=0;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,115200); //串口初始化为115200
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
printf("\r\n Hello Windows! \r\n\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%200==0)printf("Hello Windows!\r\n");
delay_ms(10);
}
}
}
五、STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。
代码如下:
串口函数Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIOA_9初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
//GPIOA_10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//选择A6为亮灯口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //模式,选择收发模式
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Serial_SendArray(uint8_t *Array,uint16_t Length)//发送数组
{
uint16_t i;
for(i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *string)
{
uint8_t i;
for(i=0;string[i]!='\0';i++)
{
Serial_SendByte(string[i]);
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
uint8_t i;
for(i=0;i<Length;i++)
{
Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+'0');//由十进制的高位到低位依次发送
//加上'0'就转换为ascll码的类型,从ascll码中字符0的位置开始算,也可以改为0x30.
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/ //将printf的底层重定向到自己的发送字节函数
//什么是重定向?重定向是指将fputc里面的输出指向目标设备。因printf函数调用了fputc,
//而fputc输出有默认指向的目标,且不同库中的fputc输出指向不同,所以需要重写fputc
int fputc(int ch,FILE *f) //printf重定向,为串口
{
Serial_SendByte(ch);
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Serial_Printf(char *format, ...)
{
char string[100];
va_list arg; //参数列表变量
va_start(arg,format); //
vsprintf(string,format,arg);
va_end(arg);//释放参数表
Serial_SendString(string);//发送string
}
主函数main.c
#include "stm32f10x.h" // Device header
#include "Serial.h"
//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口
uint8_t KeyNum;
uint8_t RxData;
int main()
{
OLED_Init();
Serial_Init();
GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);//将A6口初始化为电平
//GPIO_SetBits(GPIOA,GPIO_Pin_6);另一种初始化函数
while(1)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)//若接收寄存器数据转到RDR中,则RXNE标志位置一,标志位自动清零
{
RxData=USART_ReceiveData(USART1);
if(RxData=='Y')
{GPIO_ResetBits(GPIOA,GPIO_Pin_6);}
if(RxData=='N')
{GPIO_SetBits(GPIOA,GPIO_Pin_6);}
}
}
}
参考博客: