STM32之串口

1. 通信协议

• 假如,如果有两个芯片要通信该怎么办,这时候就需要用到串口了。

• 串口,其实就是一种通信接口,是可以用来传输数据的,如图:

• 这里的关键是两个引脚要交叉接线,从而可以实现双向通信。Tx --- Rx Rx --- Tx。

1.1 通信协议-》其实就是通信的格式

• 如图:

• 这是如果,串口不工作的时候(空闲状态下),总线上是表现出高电压状态。

• 如果突然出现了低电压,就说明 数据传输 的开始。

• 后8位或者9位表示的数据位。

• 如果启动了校验,数据位后面会紧接着校验位。

• 最后停止位,表示一帧数据传输的结束,停止位表现的是 高电压。

1.2 而这里有三处地方可以配置的

• 数据位的长度可以是8位,也可以是9位。

• 可以有校验位,也可以没有检验位。

• 停止位的长度也可以配置的。

• 因此,停止位是1位的情况下,可以得出四种不同的配置方法,如图:

1.3 例子

• 如图

• 串口传输的是二进制数据,这里会把27转换为二进制0001 1011,并且是低位先传输。

• 发送字符串,首先会把每个字符的ASCII码找出来,然后在转化对应的二进制,然后再传输。

1.3.1 检验位,也分为奇校验和偶校验

• 奇校验:要求数据位中有奇数个1。

• 偶校验:要求数据位中有偶数个1。

• 例子,这里发送这一帧数据用的是,奇校验,发送85,转换为二进制之后数为0101 0101,这里有4个1,所以为了达到奇校验,所以校验位写1。

• 在发送的时候把校验位发送给接收方,接收方得到校验位之后,会重新计算一帧数据有多少个1,看和校验位是不是相等,如果不相等,就说发送过程出现错误。

• 如图:

2. USART(其实就是串口)

2.1 基本用法

• 串口的框图:

• 假如需要发送数字100发出去,首先会把100变成二进制数,然后放到发送数据寄存器中,然后通过Tx引脚发送出去。

• 假如需要接收88,首先会从Rx引脚那里接收一组二进制数,然后把二进制数存放到接收数据寄存器中,然后转变为十进制88。

2.2 移位寄存器和串并转换

2.2.1 串行 和 并行 的概念

• 串行通信:数据按位顺序 一次传输一个bit,即一次只传输一个bit的数据。

• 并行通信:多个bit同时传输,即一次可以传输多个bit的数据。

• 如图,发送数据寄存器里面保存待发送的数据,然后发送数据寄存器通过 并行传输 给发送移位寄存器,之后发送数据寄存器会被清除。还有一个接收移位寄存器,接收传输进来的数据,然后再并行传输给接收数据寄存器。

2.2.1.1 发送移位寄存器并到串的意思

• 并:发送数据寄存器里面的数据 并行传输 给发送移位寄存器。

• 串:待发送的数据到了发送移位寄存器里面,之后会通过Tx引脚,一个数据一个数据进行发送。如图所示: • 如上图例子:数据一位一位地向外发送,并且数据也会一位一位在移位寄存器里面向前移动,直到移动完为止。

2.2.1.2 接收移位寄存器串到并的意思

• 如图:

• 串:Rx引脚接收到的输入数据,需要一位一位向前移动,在接收移位寄存器中。直到接收完一帧数据之后,才停止移动。

• 并:在接收完一帧数据,并且数据停止移动的时候,接收移位寄存器的值 通过 并行传输 发送给接收数据寄存器。并且并行传输完之后,会清空接收移位寄存器,准备下次接收。

2.2.2 数据帧格式的转换方法

• 如图,是通过这一部分控制电路配置的。

2.2.3 波特率的设置方法

• 如图

• 波特率:每秒钟最多传输多少位数据,这其中发送位越多,传输速度就越快。

• 如何转换的,如图

• 这里的分频器是对时钟信号进行除法操作。

• 得出的结果就是寄存器里面保存的值。

• 上述是算出一个比特率的步骤。

2.3 函数接口

• 如图

2.4 USART模块的引脚

• 如图:

• 一共有5个引脚,但是主要是用TX和RX引脚。

2.4.1引脚分布表

• 可以通过查数据手册,如图:

2.4.2 重映射表

• 如图(部分):

• 重映射,可以理解为"备用"。

2.4.3 IO模式配置表

• 如图:

• 这里的RX全双工,建议使用的上拉输入:

• 如果使用的是浮空,在RX引脚断了的情况下,可能会遭到外界的电磁干扰。

• 为什么不用下拉,这是因为串口总线电平的空闲的时候是高电压,如果用低电压,这可能导致总线认为这是一个开始信号。

2.5 标志位

2.5.1 TxE标志位

• TxE标志位是发送数据寄存器空的意思。

• 判断发送数据寄存器是否有数据。

2.5.2 TC标志位

• TC标志位是发送完成,就是两个寄存器都为空了,发送数据寄存器和发送移位寄存器为空。

3. 编程接口

3.1 USART_Cmd:使能

3.2 USART_GetFlagStatus:获得标志位的状态,从而可以知道串口的状态

3.3 USART_SendData:发送数据

3.4 实践

• 示例代码

cpp 复制代码
#include "stm32f10x.h"
#include "stdio.h"
#include "delay.h"

void My_Usart_SendDatas(USART_TypeDef* USARTx,uint8_t * data,uint16_t size);
void My_Usart_Init();

int main(void)
{
    Delay_Init();
    
    My_Usart_Init();

    //uint8_t datas[5] = {1,2,3,4,5};
    
    //My_Usart_SendDatas(USART1,datas,5);
    
    //printf("hello world\r\n");
    	while(1)
	{
          uint32_t currentTime = GetTick();//获取单片机当前时间
        
          uint32_t millSeconds = currentTime % 1000;//毫秒
          currentTime = currentTime / 1000;
        
          uint32_t seconds = currentTime % 60;//秒
          currentTime = currentTime / 60;
        
          uint32_t minutes = currentTime % 60;//分
           currentTime = currentTime / 60;
        
          uint32_t hours = currentTime;
        
          printf("%02u:%02u:%02u.%03u",hours,minutes,seconds,millSeconds);
        
          Delay(100);
	}
}

int fputc(int ch,FILE* f){//重写fputc函数
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//一直等,等到数据寄存器为空
    
    USART_SendData(USART1,(uint8_t)ch);//格式化字符串 重定向 到串口
    
    return ch;
    
}

void My_Usart_SendDatas(USART_TypeDef* USARTx,uint8_t * data,uint16_t size){
    
    for(uint32_t i = 0; i < size; i++){
        while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);//一直等,等到数据寄存器为空
        
        USART_SendData(USARTx,data[i]);
    }
    
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);//等待移位寄存器发完
    USART_ClearFlag(USARTx,USART_FLAG_TC);
}

//初始化USART1,并且波特率是115200,8位数据位,1位停止位,没有校验位
void My_Usart_Init(){
    //配置GPIO
    //这是使用重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
    GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);//重映射USART
    
    GPIO_InitTypeDef gpio_initstruct = {0};
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    //TX
    gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
    gpio_initstruct.GPIO_Pin = GPIO_Pin_6;
    gpio_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
    
    GPIO_Init(GPIOB,&gpio_initstruct);
    
    //RX
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;
    gpio_initstruct.GPIO_Pin = GPIO_Pin_7;
    
    GPIO_Init(GPIOB,&gpio_initstruct);
    
    //配置的是USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
    
    
    USART_InitTypeDef usart_initstruct = {0};
    
    usart_initstruct.USART_BaudRate = 115200;
    usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    usart_initstruct.USART_Parity = USART_Parity_No;
    usart_initstruct.USART_StopBits = USART_StopBits_1;
    usart_initstruct.USART_WordLength = USART_WordLength_8b;
    
    USART_Init(USART1,&usart_initstruct);//初始化USART1
    
    USART_Cmd(USART1,ENABLE);//使能USART1
    //这是使用复用功能
    //配置GPIO
    //GPIO_InitTypeDef gpio_initstruct = {0};
    
//    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//    //TX
//    gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
//    gpio_initstruct.GPIO_Pin = GPIO_Pin_9;
//    gpio_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
//    
//    GPIO_Init(GPIOA,&gpio_initstruct);
//    
//    //RX
//    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//    gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;
//    gpio_initstruct.GPIO_Pin = GPIO_Pin_10;
//    
//    GPIO_Init(GPIOA,&gpio_initstruct);

}

4. 接收数据

4.1 标志位

• RxNE:接收数据寄存器非空。如图:

4.2 错误的标志位

4.2.1 PE:奇偶校验错,如图:

4.2.2 FE:帧格式错误,如图:

4.2.3 NE:噪声错,这个对一个bit数据进行多次采样的,如果每次采样都不一样,就说明有噪声错。如图:

4.2.4 ORE:过载错,覆盖移位寄存器的值,来不及取走数据。如图:

5. 实践

• 用串口控制led,示例代码:

cpp 复制代码
#include "stm32f10x.h"
#include "stdio.h"
#include "delay.h"


void My_Usart_Init();
void My_USART_Receive();
void My_LED_Init();


int main(void)
{

    
    My_Usart_Init();
    My_LED_Init();
    
   while(1)
   {
        while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);//一直等接收数据寄存器有数据
        
        uint16_t byteRecv = USART_ReceiveData(USART1);//接收数据
        
        if(byteRecv == '0'){//灭
            GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
        }else if(byteRecv == '1'){//亮
            GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
        }
       USART_ClearFlag(USART1,USART_FLAG_RXNE);   
    }
}


void My_LED_Init(){
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    
    GPIO_InitTypeDef gpio_initstruct = {0};
    
    gpio_initstruct.GPIO_Mode = GPIO_Mode_Out_OD;
    gpio_initstruct.GPIO_Pin = GPIO_Pin_13;
    gpio_initstruct.GPIO_Speed = GPIO_Speed_2MHz;
    
    GPIO_Init(GPIOC,&gpio_initstruct);
    
    GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//灭
}


//初始化USART1,并且波特率是115200,8位数据位,1位停止位,没有校验位
void My_Usart_Init(){
    //配置GPIO
    //这是使用重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
    GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);//重映射USART
    
    GPIO_InitTypeDef gpio_initstruct = {0};
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    //TX
    gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
    gpio_initstruct.GPIO_Pin = GPIO_Pin_6;
    gpio_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
    
    GPIO_Init(GPIOB,&gpio_initstruct);
    
    //RX
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;
    gpio_initstruct.GPIO_Pin = GPIO_Pin_7;
    
    GPIO_Init(GPIOB,&gpio_initstruct);
    
    //配置的是USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
    
    
    USART_InitTypeDef usart_initstruct = {0};
    
    usart_initstruct.USART_BaudRate = 115200;
    usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    usart_initstruct.USART_Parity = USART_Parity_No;
    usart_initstruct.USART_StopBits = USART_StopBits_1;
    usart_initstruct.USART_WordLength = USART_WordLength_8b;
    
    USART_Init(USART1,&usart_initstruct);//初始化USART1
    
    USART_Cmd(USART1,ENABLE);//使能USART1
    //这是使用复用功能
    //配置GPIO
    //GPIO_InitTypeDef gpio_initstruct = {0};
    
//    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//    //TX
//    gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
//    gpio_initstruct.GPIO_Pin = GPIO_Pin_9;
//    gpio_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
//    
//    GPIO_Init(GPIOA,&gpio_initstruct);
//    
//    //RX
//    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//    gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;
//    gpio_initstruct.GPIO_Pin = GPIO_Pin_10;
//    
//    GPIO_Init(GPIOA,&gpio_initstruct);

}
相关推荐
EVERSPIN1 小时前
灵动代理mcu单片机机器人解决方案
单片机·嵌入式硬件·机器人
Nautiluss1 小时前
一起调试XVF3800麦克风阵列(六)
人工智能·单片机·音频·语音识别·dsp开发·智能硬件
A9better1 小时前
嵌入式开发学习日志47——任务创建与就绪列表
单片机·嵌入式硬件·学习
Molesidy1 小时前
【FreeRTOS】【MCU】【APM32】基于APM32标准库+FreeRTOS的APM32工程搭建
单片机·嵌入式硬件
先知后行。1 小时前
FreeModbus
单片机·嵌入式硬件
cchjyq2 小时前
嵌入式按键调参:简洁接口轻松调参(ADC FLASH 按键 屏幕参数显示)
c语言·c++·单片机·mcu·开源·开源软件
xyd陈宇阳2 小时前
C 语言宏定义(#define)语法与用法大全
c语言·嵌入式硬件
Jason_zhao_MR2 小时前
米尔T113核心板的农机中控屏显方案解析
linux·嵌入式硬件·嵌入式·交互
IT方大同2 小时前
TIM(定时器概要)
单片机·嵌入式硬件
d111111111d2 小时前
STM32 HAL库定时器PWM输出全攻略:从零到精准控制
笔记·stm32·单片机·嵌入式硬件·学习