引言
对于很多人来说引脚角标,引脚含义非常模糊,只能靠网上的教程接线。
也对于很多人来说,通信协议约等于移植现成代码。
但是对于前沿设备,前言元件,大家有没有想过自己去独立完成!而不是漫天找资料,就算找到资料大概率也会就已出现资料付费的窘境!但在你买完代码后会发现,其实没有你想的那么复杂与惊艳。
那么今天我将以U串口通信为例带领大家学一学如何构建我们自己的库!
名词解释
TX:发送数据
RX:接收数据
SW_RX
SW_RX:数据接收(内部数据接收)(无外部引脚)
硬件按控制流
硬件控制流一般用在串口通信上,他的模块是最典型的特征就是出现了RTS与CTS。它的作用就和名字一样,通过硬件信号来控制数据的传输数量与速度!!!为什么要这么做呢,为什么要通过硬件信号来控制信号传输呢!这就涉及到了“竞争与冒险”,没事,没有学过数电不要怕。我简单来说下吧:
竞争与冒险
这个电路我们当然知道结果应该是“0”,但是在实际生活中!这两个信号并不会同时到达我们的与门!他们的波形之间会有延迟,导致到与门的信号并不相反了!!!
从而产生一段时间仅微秒级的逻辑错误!!!!!!这就是“竞争与冒险”
所以我们硬件控制流做到了当所有信号全部到达与门时,才进行逻辑运算。这样子就消除了“竞争与冒险”
那么就好解释这两个引脚信号了:
RTS:request to send 请求发送:当引脚被拉为高电平时开始发送信号。
nRTTS:这里的n代表的时逻辑相反!!!!因此引脚拉低才开始发送信号。
CTS:clear to send :清晰(允许)发送:当引脚拉高时,允许发送的信号进入我的缓冲区,当拉低时不接受信号。
nCTS:逻辑相反即可。
SCLK:同步时钟引脚。
但是在我们的STM32中很明显没有后面三个引脚,所以我们的STM32的USART是异步通信,单信号传输。不用担心上面出现的情况。
另外在配置时我们一定要考虑,我们使用的USART挂在位置!!APB2 时钟频率最大72MHz而APB1时钟频率最大才36MHz。很多人移植什么都不看所有工作频率都是50MHz是非常错误的!
通信协议
通信协议才是进行通信的核心,人说人话,猫说猫语,所以只有把协议具象化才能进行目标通信!
所以USART的通信协议如下:
附带官网的配置教程:
但是要知道,既然STM32能进行USART通信,就一定有相关的固件库函数!
所以,他和GPIO口的初始化还有啥区别!但因为有标志位操作所以少不了中断服务函数!
初始化
void usart1_init(void)
{
USART_InitTypeDef usart1;
GPIO_InitTypeDef gpio1;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
gpio1.GPIO_Mode=GPIO_Mode_AF_PP;
gpio1.GPIO_Pin=GPIO_Pin_9;
gpio1.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&gpio1);
gpio1.GPIO_Mode=GPIO_Mode_IPU;
gpio1.GPIO_Pin=GPIO_Pin_10;
gpio1.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&gpio1);
usart1.USART_BaudRate=9600;
usart1.USART_HardwareFlowControl=USART_HardwareFlowControl_None ;
usart1.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
usart1.USART_StopBits=USART_StopBits_1;
usart1.USART_WordLength=USART_WordLength_8b;
usart1.USART_Parity=USART_Parity_No;
USART_Init(USART1, &usart1);
NVIC_InitTypeDef nvic1;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
nvic1.NVIC_IRQChannel= USART1_IRQn;
nvic1.NVIC_IRQChannelCmd=ENABLE;
nvic1.NVIC_IRQChannelPreemptionPriority=1;
nvic1.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&nvic1);
USART_ClearFlag(USART1, USART_FLAG_RXNE);
USART_ITConfig(USART1, USART_FLAG_RXNE,ENABLE);
USART_Cmd(USART1, ENABLE);
}
全部配置好我们就需要根据协议来写三种函数:接收函数,发送函数,中断函数
并且这些函数的基本体我们库函数中都有!!!不需要你自己去写寄存器!!!
两个头文件
#include <stdio.h>
stdio.h
是标准输入输出(Standard Input Output)的头文件。它包含了进行输入输出操作的函数声明,比如 printf()
用于向标准输出(通常是屏幕)打印格式化的字符串,scanf()
用于从标准输入(通常是键盘)读取格式化的输入,以及文件操作函数如 fopen()
, fread()
, fwrite()
, fclose()
等。基本上,任何与输入输出相关的操作,都可能需要包含 stdio.h
头文件。
#include <stdarg.h>
stdarg.h
是标准参数(Standard Arguments)的头文件。这个头文件提供了对可变参数(即函数参数的数量和类型在编译时未知)的支持。这在编写像 printf()
这样可以接受任意数量和类型参数的函数时非常有用。通过 stdarg.h
提供的宏和类型定义,程序可以遍历并访问传递给函数的可变参数列表。这个头文件主要与宏 va_list
, va_start()
, va_arg()
, 和 va_end()
相关联,这些宏允许程序以类型安全的方式访问可变参数列表中的参数。
简而言之,#include <stdio.h>
用于包含输入输出操作相关的函数声明,而 #include <stdarg.h>
用于支持编写可以接受可变数量参数的函数。这两个头文件在C语言编程中都非常常用。
所以我们在使用Printf函数时均需要调用!
void usar1_s1(uint16_t byte)
{
USART_SendData(USART1, byte);
while(USART_GetITStatus(USART1, USART_FLAG_RXNE)==RESET);
}
void usart1_s(uint8_t *arry,uint16_t legth)
{
uint16_t i=0;
for(i=0;i<legth;i++)
{
usar1_s1(arry[i]);
}
}
void usart1_st(uint8_t *string)
{
uint16_t i;
for(i=0;string[i]!='\0';i++)
{
usar1_s1(string[i]);
}
}
中断函数
void USART1_IRQhandler(void)
{
static uint8_t RXdata;
static uint8_t PACK=0;
if(USART_GetITStatus(USART1, USART_FLAG_RXNE)==SET)
{
uint8_t RXdata=USART_ReceiveData(USART1);
RXPACK[PACK]=RXdata;
usar1_s1(RXPACK[PACK]);
PACK++;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
printf函数
我们可以重定义也可以自己构建!
int fputc(int ch, FILE *f)
{
usart_s1(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void usart1_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
usart1_st(String); //串口发送字符数组(字符串)
}
这次你已经自主完成了USART串口通信的库!