串口通信——基于STM32MP157A

本实验将利用UART总线,采用串口通信的方式,发送字符串

一:串口通信方式

1:直连方式

        串口共有三根线:RXD(接收数据线)、TXD(发送数据线)、GND(接地线)

        UART总线连接的是soc与target,串口与串口相连时,RXD与对方的TXD相连,GND与GND相连。

 

 2:USB转串口的连接方式

        SOC采用的是TTL电平,TTL高电平为+5V,低电平为0V;串口电平为RS232电平,高电平为+15v~+3v,低电平为-15v~-3v

 3:ST-LINK仿真器

        ST-LINK仿真器的作用,既是完成串口和usb口的转换。在ST-LINK仿真器内部有一个芯片(STM32F103),这个芯片,完成USB口和串口之间转换(在STM32F103内部固化一段程序,这段程序不开源,这段程序可以USB口和串口之间转换)。

 4:通信协议

串口通信的协议如图所示

 此为一个完整的数据帧。

在电脑的串口工具内配置串口信息时,设置位112500波特率,8N1。

8N1的意思为:8位数据位,N(无奇偶校验位),1位停止位

如下图所示 

 协议解析

        空闲态:UART总线不在传输数据的时候,总线处于空闲状态,为高电平

        起始信号:通信开始的标志

        数据位:位数由自己指定,注意,数据先发送高位,再发送低位(简单的来说,就是要反着填入数据)

        奇偶校验位:校验数据是否正确(个人感觉没啥用)

                        奇校验:数据位的1和校验位的1加起来数量为奇数

                        偶校验:数据位的1和校验位的1加起来数量为偶数

        停止信号:数据发送完毕以后,回到高电平,校准时钟信号

校验时钟信号的必要性

        因为串口通信为异步通信,通信双方的时钟源相互独立。虽然一开始设置了双方时钟源保持一致,但是在实际发送数据的过程中,每发送一帧数据,都会产生误差,越往后,误差会越累积越大,最终变得难以忽视。为了避免这种情况的发生,每发送一帧数据,都需要进行一次时钟信号校准。

二:分析电路图

先找到UART总线的TX端口和RX端口 

 再去主板上寻找对应的引脚

 

 所以

UART4_RX------>PB2
UART4_TX------>PG11

显然,这俩是GPIOB与GPIOG寄存器管的

三:分析芯片手册

由之前的原理图和电路图可知,我们要用到两类寄存器,GPIO和UART,具体为GPIOB、GPIOG、UART4

1:RCC章节分析

老样子,先在芯片手册里找总线,GPIO前两个实验找过了,在AHB4总线

 UART在APB1总线

 我们本次编写程序依旧采用现成的库,就不去在意起始地址和地址偏移量了,如果想自己封装地址结构体,可以参考我的点亮LED灯——基于STM32MP157A_老K殿下的博客-CSDN博客(引流.jpg)

即为:
RCC_MP_AHB4ENSETR[1] = 1
RCC_MP_AHB4ENSETR[6] = 1

RCC_MP_APB1ENSETR

即为
RCC_MP_APB1ENSETR[16] = 1

2:GPIO章节分析

GPIOx_MODER

设置PB2和PG11引脚为复用功能
    GPIOB_MODER[5:4] = 10 ------->设置PB2引脚为复用功能
    GPIOG_MODER[23:22] = 10 ------->设置PG11引脚为复用功能

GPIOx_AFRx寄存器

有AFRL和AFRH两个寄存器,两个引脚分别位于AFRL和AFRH上

首先分析AFRL上的

 然后到《stm32mp157a》芯片手册上查看

 故,选择AF8模式,即1000

设置PB2引脚为复用功能UART4_Rx
    GPIOB_AFRL[11:8] = 1000 ------->设置PB2引脚为复用功能UART4_Rx

 然后是PG11引脚,它在AFRH寄存器上

  

设置PG11引脚为复用功能UART4_Tx
    GPIOG_AFRH[15:12] = 0110  ------->设置PG11引脚为复用功能UART4_Tx

3:UART章节

通过以上分析可知,设置寄存器:
1.USART_CR1:设置数据位宽度,以及将相应位进行使能
2.USART_CR2:设置停止位
3.USART_BRR:设置波特率---->设置的采样率有关
4.USART_RDR :设置接收数据寄存器
5.USART_TDR :设置发送数据寄存器
6.USART_ISR:设置状态寄存器
7.USART_PRESC :设置时钟分频器

 USART_CR1寄存器

 

 

 

 

 

 

需要设置如下位:
USART_CR1[28][12] = 00 ------->设置串口8位数据位
USART_CR1[15] = 0 ------->设置串口16倍采样率,会影响波特率的计算
USART_CR1[10] = 0------->设置串口无奇偶校验位
USART_CR1[3] = 1------->设置串口发送寄存器使能
USART_CR1[2] = 1------->设置串口接收寄存器使能
USART_CR1[0] = 1------->设置串口接收使能

USART_CR2寄存器

 

 USART_CR2[13:12] = 00 ------->设置串口1位停止位

 USART_BRR寄存器

设置波特率

 波特率的计算方法为:

需要参考芯片手册53.5.7章节,进行计算,波特率寄存器的设置,与采样率有关
设置串口波特率为115200bps,系统提供的串口时钟源:64MHZ
BRR = 64MHZ / 115200 = 0x22b
USART4_BRR = 0x22b ------->设置串口波特率为115200

USART_RDR寄存器

RDR寄存器是存放接收数据的寄存器

 USART_TDR寄存器

TDR寄存器是存放要发送的数据的寄存器,数据存放在[8:0]位

 USART_PRESC寄存器

仅能在[3:0]位输入数据,设置不分频USART_ISR寄存器

 USART_ISR寄存器

  

 

 至此,全部寄存器分析完毕,接下来是代码实现

四:代码实现

因为寄存器部分代码已经分析过了,就不再浪费时间逐一填,直接附上初始化UART4的功能函数源代码

寄存器初始化

// 初始化的函数
void uart4_init()
{
    // 1. 使能GPIOB,GPIOG外设的时钟 RCC_MP_AHB4ENSETR[1][6] = 0b1
    RCC->MP_AHB4ENSETR = (0x1 << 1) | (0x1 << 6);
    // 2. 设置PB2, 和 PG11引脚为复用的功能
            // GPIOB_MODER[5:4] = 0b10  GPIOG_MODER[23:22] = 0b10
    GPIOB->MODER &= (~(0x3 << 4));
    GPIOB->MODER |= (0x2 << 4);
    GPIOG->MODER &= (~(0x3 << 22));
    GPIOG->MODER |= (0x2 << 22);
 
    // 3. 设置PB2引脚为UART4_RX功能  GPIOB_AFRL[11:8] --> AF8 --> 0b1000
        // 设置PG11引脚为UART4_TX功能  GPIOG_AFRH[15:12] --> AF6 --> 0b0110
    GPIOB->AFRL &= (~(0xF << 8));
    GPIOB->AFRL |= (0x8 << 8);
    GPIOG->AFRH &= (~(0xF << 12));
    GPIOG->AFRH |= (0x6 << 12);
 
    // 4. 使能UART4外设的时钟  RCC_MP_APB1ENSETR[16] = 0b1
    RCC->MP_APB1ENSETR = (0x1 << 16);
 
    // 5. 判断UART4串口是否使能,如果使能则禁止串口
    if (USART4->CR1 & (0x1 << 0)) {
        delay_ms(2000);  // 等待之前的串口的数据发送完成之后在禁止串口
        USART4->CR1 &= (~(0x1 << 0));  // 禁止串口的使能
    }
    // 6. 设置数据位为8位的数据宽度 USART4_CR1[28][12] = 0b00
    USART4->CR1 &= ~((0x1 << 12) | (0x1 << 28));
    // 7. 禁止校验位,不使用校验  USART4_CR1[10] = 0b0
    USART4->CR1 &= (~(0x1 << 10));
    // 8. 设置串口的采样率为16倍或者8倍,最终会影响波特率的计算  USART4_CR1[15]
    USART4->CR1 &= (~(0x1 << 15));
    // 9. 设置停止位的个数为1位  USART4_CR2[13:12] = 0b00
    USART4->CR2 &= (~(0x3 << 12));
    // 10. 设置串口时钟的分频寄存器 USART4_PRERC[3:0]  最终也会影响波特率的计算
            // usart_ker_ck 时钟源的频率等于 64MHz
            // usart_ker_ck_pesc = usart_ker_ck / USART4_PRESC[3:0]
     USART4->PRESC &= (~(0xF << 0));
     // 11. 设置串口的波特率为115200bps  USART4_BRR[15:0]                    
     USART4->BRR = 0x22B;                                                    
     // 12. 使能串口发送器  USART4_CR1[3] = 0x1                              
     USART4->CR1 |= (0x1 << 3);                                              
     // 13. 使能串口接收器  USART4_CR1[2] = 0x1                              
     USART4->CR1 |= (0x1 << 2);                                              
     // 14. 使能串口        USART4_CR1[0] = 0x1                              
     USART4->CR1 |= (0x1 << 0);                                              
 }                                            

 头文件用别人写好的、现成的库

#include "stm32mp1xx_uart.h"
#include "stm32mp1xx_rcc.h"
#include "stm32mp1xx_gpio.h"
#define LEN  50

现在的关键,在于字符串发送接收函数的实现

我们要根据串口的通信协议,手动模拟串口数据包(如果谁忘了通信协议是什么样的,自行往上翻阅,写在开头)

要发送字符串,先要实现单个字符的收发

发送单个字符

void put_char(const char str)
{
    //1.判断发送数据寄存器是否有数据 ISR[7]
    //读0:发送数据寄存器满,需要等待
    //读1:发送数据寄存器为空,才可以发送下一个字节数据
    while(!(USART4->ISR & (0x1 << 7)));

    //2.将要发送的字符,写入到发送数据寄存器中
    USART4->TDR = str;

    //3.判断发送数据是否发送完成
    //读0:发送数据没有完成,需要等待
    //读1:发送数据完成,可以发送下一帧数据
    while(!(USART4->ISR & (0x1 << 6)));
}

接收一个字符

char get_char()
{
    char ch;
    //1.判断接收寄存器是否有数据可读 ISR[5]
    //读0:没有数据可读,需要等待
    //读1:有数据可读
    while(!(USART4->ISR & (0x1 << 5)));

    //2.将接收数据寄存器中的内容读出来
    ch = USART4->RDR;
    return ch;
}

发送字符串

发送字符串有些许不同,要手都补上换行的功能,在pc中“回车”是相当于'\n'与'\r'的组合,\n是换行,\r是回到行首,所以,在写代码时,要手动补上这两个字符

//3.发送一个字符串
void put_string(const char* str)
{
    //判断是否为'\0'
    //一个一个字符的进行发送
    while(*str)
    {
        put_char(*str++);
    }
    put_char('\n');
    put_char('\r');
}

 接收字符串

收到的字符串,存在数组中

char buffer[50] = {0};

char* get_string()
{
    unsigned int i;
    //1.循环进行接收
    //2.循环实现:接收一个字符之后,发送一个字符
    //当键盘回车建按下之后,代表字符串接收结束'\r'
    for(i=0;i<49;i++)
    {
        buffer[i] = get_char();
        put_char(buffer[i]);
        if(buffer[i] == '\r')
            break;
    }
    //3.字符串补'\0'
    buffer[i] = '\0';
    put_char('\n');
    return buffer;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
STM32MP157A是一款基于Arm Cortex-A7和Cortex-M4内核的多核片上系统。它具有丰富的中断控制器和中断处理机制,可以实现多种类型的中断处理,包括外部中断、定时器中断、DMA中断、UART中断、SPI中断等。下面是关于STM32MP157A中断的一些基本知识: 1. 中断优先级:STM32MP157A支持多个中断请求,每个中断请求都有一个优先级。当多个中断请求同时发生时,系统会根据中断请求的优先级来确定中断处理程序的执行顺序。通常情况下,优先级越高的中断请求会先被处理。 2. 中断源:STM32MP157A支持多种中断源,包括外部事件、定时器、串口、SPI、I2C、DMA等。这些中断源都有自己的中断请求线,当中断源发生时,它会向中断请求线发送中断请求信号。 3. 中断控制器:STM32MP157A中使用了多个中断控制器,包括EXTI、GIC和NVIC。EXTI是一个外部中断控制器,用于处理外部事件的中断;GIC是一个通用的中断控制器,用于管理和处理所有的中断请求;NVIC是一个嵌套向量中断控制器,用于处理中断请求。 4. 中断处理程序:中断处理程序是实现中断处理的代码块,它会在中断请求发生时被调用。中断处理程序通常会根据中断类型执行相应的操作,例如读取数据、写入数据、清除中断标志位等。 5. 中断处理方式:在STM32MP157A中,中断处理可以使用两种方式:中断服务例程(ISR)和中断向量表。中断服务例程是一段特殊的函数,它会在中断请求发生时被调用。中断向量表是一张包含中断处理程序地址的表格,它会在中断请求发生时被读取,然后跳转到相应的中断处理程序。 总的来说,STM32MP157A中的中断处理机制非常灵活和可靠,可以满足各种不同类型的中断处理需求。在实际应用中,需要根据具体的需求选择合适的中断源和中断处理方式,并注意中断优先级的设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老K殿下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值