本实验将利用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;
}