串口通信
- 1、串口简介
- 2、串口通讯协议
- 3、硬件外设
- 4、发送数据
- 5、使用轮询的方式接收数据(USART1)
- 6、使用中断的方式接收数据
- 7、串口进行printf重定向
1、串口简介
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。
2、串口通讯协议
1)波特率
“波特率”(Baudrate),它表示每秒钟传输了多少个码元。在二进制的世界码元和位是等价的。用每秒传输的比特数表示波特率。STM32提供的是串口异步通讯,异步通讯中由于没有时钟信号,所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为4800、9600、115200等。
2)空闲位
串口协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据。
3)通讯的起始位
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
4)通讯的停止位
停止信号可由 0.5、1、1.5 或 2个逻辑1的数据位表示,只要双方约定一致即可。
5)有效数据位
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或8位长。构成一个字符(一般都是8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
6)校验位
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。串口校验分几种方式:
(1)无校验(no parity)。
(2)奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
7) 空闲帧
通过检测空闲帧来判断数据是否接收完(相当于结束符)。检测到空闲帧也可以触发空闲帧中断
3、硬件外设
stm32的USART中结构框图如下:
2)波特率的产生
发送器和接收器的波特率是一致的,都是通过设置BRR寄存器来得到。
这里的是给外设的时钟(usart1在APB2上一般是72MHz,usart2,3,4,5在APB1上一般为36MHz)。
假设我们需要的波特率是115200,则对应的分频值应该是:39.0625,把这个值写入到BRR寄存器中。39.0625的小数部分:0.0625 * 16 = 1, 整数部分是:39(0x27)。
所以写入到BRR寄存器的值是:0x0271。
4、发送数据
①USATR1.c文件的代码如下:
#include "USART.h"
/*
* 串口USART1的初始化:接收/发送
*/
void USART1_Init(void)
{
/*1.开启GPIOA的时钟和USART1的时钟*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;//开启USART1的时钟
/*2.配置PA9和PA10引脚的工作模式:PA9(TX)为复用推挽输出,PA10(RX)为浮空输入*/
GPIOA->CRH |= GPIO_CRH_MODE9;//MODE9 = 11:输出模式Speed = 50MHz
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_CNF9_1;//CF9 = 10:复用推挽输出
GPIOA->CRH &= ~GPIO_CRH_MODE10;//MODE10 = 00:输入模式
GPIOA->CRH |= GPIO_CRH_CNF10_0;
GPIOA->CRH &= ~GPIO_CRH_CNF10_1;//CF10 = 01:浮空输入
/*3.串口的参数配置*/
/*3.1 配置波特率位115200:USART_BRR = 0x271*/
USART1->BRR = 0x271;
/*3.2 数据位长度位8位:USART_CR1_M = 0*/
USART1->CR1 &= ~USART_CR1_M; //CR1控制寄存器中M = 0
/*3.3 配置不需要校验位*/
USART1->CR1 &= ~USART_CR1_PCE;//CR1控制寄存器中PCE = 0
/*3.4 1位停止位*/
USART1->CR2 &= ~USART_CR2_STOP;//CR2控制寄存器中STOP = 00
/*3.5 使能接收与发送*/
USART1->CR1 |= USART_CR1_TE;
USART1->CR1 |= USART_CR1_RE;
/*4.使能串口*/
USART1->CR1 |= USART_CR1_UE;
}
/*
* 发送一个字节
*/
void USART1_SendByte(uint8_t Byte)
{
/*1.等待数据寄存器是否空,即发送数据寄存器空*/
while((USART1->SR & USART_SR_TXE) == 0);//表示数据寄存器非空
USART1->DR = Byte;//写入DR硬件自动置位TXE位
}
/*
* 发送一个字符串
*/
void USART1_SendString(uint8_t *str, uint16_t Length)
{
for(uint8_t i = 0; i < Length ;i++)
{
USART1_SendByte(str[i]);
}
}
②主函数文件的代码如下:
#include "stm32f10x.h"
#include "USART.h"
#include "Delay.h"
#include "String.h"
uint8_t Array[5] = {'A','B','C','D','E'};
uint8_t *String = "Hello World\r\n";
int main(void)
{
USART1_Init();
USART1_SendString(Array, 5);//向上位机发送字符数组Array
USART1_SendString(String, 14);//向上位机发送字符串String
while(1)
{
}
}
5、使用轮询的方式接收数据(USART1)
①USAR1.c文件的代码如下:
#include "USART.h"
/*
* 串口USART1的初始化:接收/发送
*/
void USART1_Init(void)
{
/*1.开启GPIOA的时钟和USART1的时钟*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;//开启USART1的时钟
/*2.配置PA9和PA10引脚的工作模式:PA9(TX)为复用推挽输出,PA10(RX)为浮空输入*/
GPIOA->CRH |= GPIO_CRH_MODE9;//MODE9 = 11:输出模式Speed = 50MHz
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_CNF9_1;//CF9 = 10:复用推挽输出
GPIOA->CRH &= ~GPIO_CRH_MODE10;//MODE10 = 00:输入模式
GPIOA->CRH |= GPIO_CRH_CNF10_0;
GPIOA->CRH &= ~GPIO_CRH_CNF10_1;//CF10 = 01:浮空输入
/*3.串口的参数配置*/
/*3.1 配置波特率位115200:USART_BRR = 0x271*/
USART1->BRR = 0x271;
/*3.2 数据位长度位8位:USART_CR1_M = 0*/
USART1->CR1 &= ~USART_CR1_M; //CR1控制寄存器中M = 0
/*3.3 配置不需要校验位*/
USART1->CR1 &= ~USART_CR1_PCE;//CR1控制寄存器中PCE = 0
/*3.4 1位停止位*/
USART1->CR2 &= ~USART_CR2_STOP;//CR2控制寄存器中STOP = 00
/*3.5 使能接收与发送*/
USART1->CR1 |= USART_CR1_TE;
USART1->CR1 |= USART_CR1_RE;
/*4.使能串口*/
USART1->CR1 |= USART_CR1_UE;
}
/*
* 接收一个字节
*/
uint8_t USART1_ReceiveByte(void)
{
/*1 等待读取数据寄存器是否为非空 */
while((USART1->SR & USART_SR_RXNE) == 0);//读数据寄存器非空(1:收到数据,可以读出)
return USART1->DR;//读取DR,硬件中断清零RXNE位
}
/*
* 接收一个变长的字符串,Size为接收到字符个数
*/
void USART1_ReceiveString(uint8_t str[],uint8_t *Size)
{
uint8_t i = 0;
while(1)
{
/* 判断当前数据帧是否接收完毕 */
while (!(USART1->SR & USART_SR_RXNE))//读数据寄存器空(0:没有收到数据)
{
if((USART1->SR & USART_SR_IDLE))//IDLE位为1,标志空闲
{
USART1->SR &= ~USART_SR_IDLE;//IDLE位要软件置0,将IDLE位置0,为下一次空闲做准备
*Size = i;
return;//跳出函数
}
}
str[i++] = USART1->DR;
}
}
②主函数文件的代码如下:
#include "stm32f10x.h"
#include "USART.h"
#include "OLED.h"
#include "String.h"
uint8_t Str[100] = {0};
uint8_t Size = 0;
int main(void)
{
OLED_Init();
USART1_Init();
OLED_ShowString(1,1,"Str:");
while(1)
{
USART1_ReceiveString(Str,&Size);
for(uint8_t i = 0;i < Size;i++)
{
OLED_ShowChar(1,i+5,Str[i]);
}
}
}
6、使用中断的方式接收数据
①USART.c文件的代码如下:
#include "USART.h"
/*
* 串口1的初始化
*/
void USART1_Init(void)
{
/*1.开启GPIOA的时钟和USART1的时钟*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;//开启USART1的时钟
/*2.配置PA9和PA10引脚的工作模式:PA9(TX)为复用推挽输出,PA10(RX)为浮空输入*/
GPIOA->CRH |= GPIO_CRH_MODE9;//MODE9 = 11:输出模式Speed = 50MHz
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_CNF9_1;//CF9 = 10:复用推挽输出
GPIOA->CRH &= ~GPIO_CRH_MODE10;//MODE10 = 00:输入模式
GPIOA->CRH |= GPIO_CRH_CNF10_0;
GPIOA->CRH &= ~GPIO_CRH_CNF10_1;//CF10 = 01:浮空输入
/*3.串口的参数配置*/
/*3.1 配置波特率位115200*/
USART1->BRR = 0x271;
/*3.2 数据位长度位8位*/
USART1->CR1 &= ~USART_CR1_M; //CR1控制寄存器中M = 0
/*3.3 配置不需要校验位*/
USART1->CR1 &= ~USART_CR1_PCE;//CR1控制寄存器中PCE = 0
/*3.4 1位停止位*/
USART1->CR2 &= ~USART_CR2_STOP;//CR2控制寄存器中STOP = 00
/*3.5 使能接收与发送*/
USART1->CR1 |= USART_CR1_TE;
USART1->CR1 |= USART_CR1_RE;
/*3.6 使能中断*/
USART1->CR1 |= USART_CR1_RXNEIE;//使能接收中断
USART1->CR1 |= USART_CR1_IDLEIE;//使能空闲中断
/*4.配置NVIC*/
NVIC_SetPriorityGrouping(4);//优先级分组
NVIC_SetPriority(USART1_IRQn,2);//抢占值为2
NVIC_EnableIRQ(USART1_IRQn);//使能中断请求
/*5.使能串口*/
USART1->CR1 |= USART_CR1_UE;
}
/*
* 中断服务函数:接收变长数据
*/
uint8_t Array[100];
uint8_t Flag = 0;
uint8_t Len = 0;
void USART1_IRQHandler(void)
{
/* 产生中断的发送很多,但是只有一个中断服务函数,所以需要判断标志位 */
if(USART1->SR & USART_SR_RXNE)//如果是接收数据产生的中断
{
Array[Len] = USART1->DR;//读取数据并自动清除中断标志位
Len++;
}
if(USART1->SR & USART_SR_IDLE)//如果是空闲产生的中断
{
USART1->SR;
USART1->DR;//清除IDLE中断标志位
Flag = 1;
}
}
②USART.h文件的代码如下:
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
void USART1_Init(void);
void USART1_SendByte(uint8_t Byte);
void USART1_SendString(uint8_t *str, uint16_t Length);
extern uint8_t Array[100];
extern uint8_t Flag;
extern uint8_t Len;
#endif
③主函数文件的代码如下:
#include "stm32f10x.h"
#include "USART.h"
#include "OLED.h"
#include "String.h"
int main(void)
{
USART1_Init();
OLED_Init();
OLED_ShowString(1,1,"Str:");
while(1)
{
if(Flag)
{
OLED_Clear();
OLED_ShowString(1,1,"Str:");
for(uint8_t i = 0;i < Len;i++)
{
OLED_ShowChar(1,i+5,Array[i]);
}
Flag = 0;
Len = 0;
}
}
}
7、串口进行printf重定向
Keil软件的一些配置:
①USART.h文件的代码如下:
#include "USART.h"
#include "String.h"
#include <stdio.h>
/*
* 串口1的初始化
*/
void USART1_Init(void)
{
/*1.开启GPIOA的时钟和USART1的时钟*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//开启GPIOA的时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;//开启USART1的时钟
/*2.配置PA9和PA10引脚的工作模式:PA9(TX)为复用推挽输出,PA10(RX)为浮空输入*/
GPIOA->CRH |= GPIO_CRH_MODE9;//MODE9 = 11:输出模式Speed = 50MHz
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_CNF9_1;//CF9 = 10:复用推挽输出
GPIOA->CRH &= ~GPIO_CRH_MODE10;//MODE10 = 00:输入模式
GPIOA->CRH |= GPIO_CRH_CNF10_0;
GPIOA->CRH &= ~GPIO_CRH_CNF10_1;//CF10 = 01:浮空输入
/*3.串口的参数配置*/
/*3.1 配置波特率位115200*/
USART1->BRR = 0x271;
/*3.2 数据位长度位8位*/
USART1->CR1 &= ~USART_CR1_M; //CR1控制寄存器中M = 0
/*3.3 配置不需要校验位*/
USART1->CR1 &= ~USART_CR1_PCE;//CR1控制寄存器中PCE = 0
/*3.4 1位停止位*/
USART1->CR2 &= ~USART_CR2_STOP;//CR2控制寄存器中STOP = 00
/*3.5 使能接收与发送*/
USART1->CR1 |= USART_CR1_TE;
USART1->CR1 |= USART_CR1_RE;
/*4.使能串口*/
USART1->CR1 |= USART_CR1_UE;
}
/*
* 发送一个字节
*/
void USART1_SendByte(uint8_t Byte)
{
/*1.等待数据寄存器是否空,即发送数据寄存器空*/
while((USART1->SR & USART_SR_TXE) == 0);//表示数据寄存器非空
USART1->DR = Byte;//写入DR硬件自动置位TXE位
}
/*
* 发送一个字符串
*/
void USART1_SendString(uint8_t *str, uint16_t Length)
{
for(uint8_t i = 0; i < Length ;i++)
{
USART1_SendByte(str[i]);
}
}
/*
* printf()重定向:重写fputc
*/
int fputc(int ch,FILE * file)
{
//直接将字符发送到串口
USART1_SendByte(ch);
return ch;
}
②主函数文件的代码如下:
#include "stm32f10x.h"
#include "USART.h"
int main(void)
{
USART1_Init();
printf("woshinibaba\r\n");//向串口输出woshinibaba
while(1)
{
}
}