STM32之串口(一)

1. 通信基础

1.1 串行通信和并行通信

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

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

1.2 全双工,半双工以及单工通信

• 单工:数据只能沿着一个方向传输。

• 半双工:数据可以沿着两个方向传输,但是需要分时进行也就是说同一时间只能有一个方向在传输数据。

• 全双工:数据可以同时沿着两个方向传输,不需要分时进行。

• 如图:

1.3 同步通信和异步通信

• 同步通信:发送方和接收方必要按照预定的时钟节拍 进行数据的发送和接收,双方的操作严格同步。如图:

• 异步通信:双方不需要严格的时钟同步,每个数据块之间通过特定的起始位和停止位进行分隔,接收方可以独立地识别每个数据块。如图:

2. 串口

• 串口,也称串行接口或者串行通信接口(通常指COM口),是一种采用串行通信方式 的扩展接口。是实现数据一位一位按顺序的传输 ,具有通信线路简单成本低但传输速度慢的特点。只要一对传输线,串口就可以实现双向通信(全双工)。

• 串口通信的接口类型 包括TTL,CMOS,RS-232和RS-485等,他们代表了不同的电平标准。

• TTL电平,逻辑1:5v,逻辑0:0v。

• CMOS电平,逻辑1:提供的电压最大值,逻辑0:0v。

• RS-232,逻辑1:-3~-15v,逻辑0:3~15v。

• RS-485:

• 采用的是差分信号,逻辑1:两线间电压的差值为+(0.2-6)v,逻辑0:两线间电压的差值为-(0.2-6)v.

• 这种采用差分信号,可以有效避免外界的干扰,影响到电平。

3. 串口发送数据

• 串口发送一帧数据的组成,由起始位,数据位,校验位 ( 可以没有 ) ,停止位组成。

• 起始位:起始位为低电平 时,告诉接收方数据传输即将开始,准备接收。在通信开始的时候,发送端首先会发送一个起始位,他是一个逻辑0(低电平)信号,用于同步发送和接收设备之间的时钟,之后会开始准备接收后续数据位。

• 数据位:数据位是由一系列二进制组成,用于传输或者接收实际数据。数据位的数量可以 决定传输的不同二进制值的数量 ,常见的有5位,6位,7位,8位(常见是8位),LSB 在前, HSB 在后。数据位紧随着起始位之后,包括了要传输的实际信息。

• 检验位:校验位用于检测数据的完整性 ,以确保传输过程中没有出现错误。常见的校验位选项中有None( 无校验位 ) Odd( 奇校验位 ) Even( 偶校验位)。在发送数据时,校验位会根据数据中的1的个数进行计算,并加入到数据中一起传输。接收端则会根据校验的值进行校验,以判断数据是否存在错误。

• 停止位:停止位是一个逻辑高电平 ,用于指示数据传输的结束。当停止位出现时,接收端知道数据传输已经完成,并且可以开始处理接收到的数据。停止位位于数据位和校验位之后,它的作用是确保接收端有足够时间来识别数据帧的结束,并为下一个数据帧的到来做好准备。

4. STM32的USART简介

• USART是Universal synchronous asynchronous receiver transmitter的简写,是通用同步异步收发器的意思。

• UART是Universal asynchronous receiver transmitter的简写,是通用异步收发器。

全双工通信 :USART支持全双工的通信,即数据可以在两个方向上同时传输(A->B且B->A)。这使得USART能够满足许多需要双向通信的场景。

同步与异步传输: 尽管USART的"S"代表同步,但在实际应用中,USART更常用于异步通信 。然而,它也支持同步通信模式,只是这种模式通常用于兼容其他协议或特殊模式,并且两个USART设备不能通过同步模式进行直接通信。

波特率发生器 :USART自带波特率发生器 ,最高可达4.5Mbits/s,可以根据需要,配置不同的波特率。

硬件流控制 :USART支持硬件流控制,通过特定的信号线(如RTS/CTS)实现数据的可靠传输。当接收端没有准备好接收数据时,可以通过RTS 信号通知发送端暂停发送;当接收端准备好接收数据时,再通过CTS信号通知发送端恢复发送。

• USART框图:

5. 实操

5.1 实战1,串口实现一个字符收发

• uart1.c

cpp 复制代码
#include "uart1.h"

UART_HandleTypeDef uart1_handle = {0};//串口的句柄

//串口初始化
void uart1_init(uint32_t baudrate){
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    //OverSampling过采样,减少误差误差。使用的是16倍。这里不用配置
    HAL_UART_Init(&uart1_handle);
}


void HAL_UART_MspInit(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        //tx和rx查芯片手册就可以。
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
    }
}

void USART1_IRQHandler(){
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET){//看是不是被置1了
        
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);//发送数据
    }
}

• main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"


int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
        led1_on();
        led2_off();
        delay_ms(500);//没隔500ms闪烁
        
        led2_on();
        led1_off();
        delay_ms(500);//没隔500ms闪烁
    }
}

5.2 实战2,串口接收下·不定长数据(接收中断)

• uart.c

cpp 复制代码
#include "uart1.h"
#include "stdio.h"
#include "string.h"
UART_HandleTypeDef uart1_handle = {0};//串口的句柄
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];//定义接收的数据存放位置
uint16_t uart1_rx_len = 0;//uart1_rx_buf数据的长度
uint16_t uart1_cnt = 0;//计数器,表示接收了多少个数
uint16_t uart1_cntPre = 0;//保存接收前一个数是第几个数

//串口初始化
void uart1_init(uint32_t baudrate){
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}

void HAL_UART_MspInit(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
    }
}
int fputc(int ch,FILE *f){//重定向printf函数,到串口助手
    while((USART1->SR & 0x40) == 0);//一直等 发送数据寄存器不为空 
    USART1->DR = (uint8_t)ch;
    return ch;
}

uint8_t uart1_wait_receive(){//统计是第几个数的函数,什么时候发送完(类似计数器)
    if(uart1_cnt == 0)//如果此时的索引uart1_cnt还是等于0,说明是出错了
        return UART1_ERROR;
    
    if(uart1_cnt == uart1_cntPre){//如果表示前一个数uart1_cntPre等于uart1_cnt说明已经接收完了。
        uart1_cnt = 0;//索引清0
        return UART1_EOK;
    }
    uart1_cntPre = uart1_cnt;//此时正在接收
    return UART1_ERROR;
    
}
void uart1_clear(){//清0数组
    memset(uart1_rx_buf,0,sizeof(uart1_rx_buf));
    uart1_rx_len = 0;
}
void uart1_test(){//测试函数
    if(uart1_wait_receive() == UART1_EOK){//如果等于,就说明接收完了
        printf("recv: %s\r\n",uart1_rx_buf);
        uart1_clear();
    }
    
}

void USART1_IRQHandler(){
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET){//看是不是被置1了
        if(uart1_cnt >= sizeof(uart1_rx_buf))
            uart1_cnt = 0;//接收前需要判断一下,,uart1_cnt是否超出总长度UART1_RX_BUF_SIZE
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        uart1_rx_buf[uart1_cnt++] = receive_data;//把接收到的数据放进数组
        
        //HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);
    }
}

• main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"


int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
        uart1_test();
        delay_ms(20);
    }
}

5.3 实战3,串口接收不定长数据(空闲中断)

• uart.c

cpp 复制代码
#include "uart1.h"
#include "stdio.h"
#include "string.h"
UART_HandleTypeDef uart1_handle = {0};//串口的句柄
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];//定义接收的数据存放位置
uint16_t uart1_rx_len = 0;//计数器,表示接收了多少个数


//串口初始化
void uart1_init(uint32_t baudrate){
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}

//串口初始化
void uart1_init(uint32_t baudrate){
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}

void HAL_UART_MspInit(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
        __HAL_UART_ENABLE_IT(huart,UART_IT_IDLE);//使能空闲中断
        //当总线接收完之后,执行空闲中断
    }
}
int fputc(int ch,FILE *f){//重定向printf函数
    while((USART1->SR & 0x40) == 0);//一直等 发送数据寄存器不为空 
    USART1->DR = (uint8_t)ch;
    return ch;
}


void uart1_clear(){
    memset(uart1_rx_buf,0,sizeof(uart1_rx_buf));
    uart1_rx_len = 0;
}


void USART1_IRQHandler(){
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET){//看是不是被置1了
        if(uart1_rx_len >= sizeof(uart1_rx_buf))
            uart1_rx_len = 0;//接收前需要判断一下,,uart1_cnt是否超出总长度UART1_RX_BUF_SIZE
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        uart1_rx_buf[uart1_rx_len++] = receive_data;//把接收到的数据放进数组
        
        //HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);
    }
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_IDLE) != RESET){//接收完,就触发了空闲中断。
        __HAL_UART_CLEAR_IDLEFLAG(&uart1_handle);//清除空闲位
        printf("recv: %s\r\n",uart1_rx_buf);
        uart1_clear();//在这里,已经把buf清空了
    }
    
}

• main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"


int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
        
    }
}
相关推荐
一条闲鱼_mytube2 小时前
智能体设计模式(六)资源感知优化-推理技术-评估与监控
网络·人工智能·设计模式
IT摆渡者2 小时前
Rocky Linux 10.1中找不到传统的 /etc/sysconfig/network-scripts 目录是正常现象。
linux·运维·服务器·网络·经验分享
想睡觉的树2 小时前
解决keil5编译慢的问题-亲测有效-飞一般的感觉
c语言·stm32·嵌入式硬件
一条闲鱼_mytube2 小时前
智能体设计模式(七)优先级排序-探索与发现
网络·人工智能·设计模式
WJ.Polar2 小时前
ensp路由配置
网络
__万波__2 小时前
STM32L475串口打印改为阻塞式打印兼DMA, 两种打印方式实时切换
stm32·单片机·嵌入式硬件
IP搭子来一个2 小时前
什么是http代理,http代理的作用是什么?
网络·网络协议·http
猫猫的小茶馆2 小时前
【Linux 驱动开发】二. linux内核模块
linux·汇编·arm开发·驱动开发·stm32·嵌入式硬件·架构
深信达沙箱2 小时前
业务系统安全办公沙箱解决方案
网络·系统安全·加密·源代码·沙盒