菜喵のSTM32F4学习笔记(1)USART
这个系列的文章几乎没有怎么润色,大约保留了自己学习过程中的原始笔记和debug记录,部分内容甚至很基础,bug出的也很没水平,大家笑一笑就好2333。
注1:本系列笔记中使用STM32F407ZGT6,利用STM32库函数进行开发;
注2:个人笔记,记录的是零散的知识,不对涉及到的背景知识进行的铺开介绍(换句话说这不是一个成体系的可以视作学习用的教材的笔记,不过可以做交流参考~);
注3:啥时候B站专栏有代码块和公式啊qwq(每篇一问(笑))

写在笔记前:
关于STM32配置操作的部分内容,个人感觉在了解了GPIO以及各个外设涉及到的寄存器之后,再来看各种库函数会有一种启明的感觉23333。
此时通过“到定义”的方式查看各个函数的实现也能比较容易地了解到这些乱七八糟的函数到底都在动那些寄存器、又干了些啥(笔记Note1中记录的就是这样的过程)。
稍微熟悉一点之后你甚至可以主动探索了:或者从各个外设的功能出发,看对应文件下定义的函数以及其中操作的寄存器;或者从文件出发,通过看函数和寄存器反推这个外设在干嘛23333。

Note1:(这部分在研究GPIO复用操作(有点细过头了2333))
首先,外设和GPIO不一定挂载在同一个时钟上,所以可能要分别使能相关时钟总线。STM32F4给了五个外设时钟使能函数:

一番搜索不难发现(stm32f4xx_rcc.h):


之后,注意到GPIO结构体没有涉及到对于AFRL &AFRH寄存器的操作,也就是说即使MODER寄存器里面选择了复用功能,复用器也没有配置。

查看GPIO_PinAFConfig函数定义(在stm32f4xx_gpio.c文件中 )可知,关于每一个GPIO口的复用器设置,还是需要用函数单独操作一下的(GPIOx->AFR)

或者换句话说,你看完之后就知道为啥GPIO复用的时候还要用这样一个函数了,而不是在GPIO_Init()中完成所有的定义(前提:你知道STM32F4当中都有哪些寄存器,分别有什么功能)。
不过这里我在想, 如果你足够巨佬也足够顽皮,你可以考虑修改一下库函数的内容(笑),比如把GPIO_PinAFConfig中的内容改写到GPIO_Init()当中,然后修改一下GPIO_InitTypeDef结构体啥的(x),这样你就可以少写一两行GPIO复用的代码了,是不是很方便23333(x)。
对于GPIO_PinAFConfig()中可选用的GPIO_AF,在stm32f4xx_gpio.h中有定义(我用的是STM32F407,故而是这一段)

可以看到,串口USART、UART都在,不区分TX、RX;所以复用的时候直接设置为GPIO_AF_USART1就好。
查阅芯片数据手册得PA9、PA10 可担当串口复用大任:

故最后GPIO复用如下:

这里就完成了将特定一组GPIO中的特定的IO口通过复用器连接到其对应复用功能上的操作了。


试验1:含中断的串口发送试验
第一次写的主程序如下(Usart_Init();为串口初始化代码,对串口1 进行初始化操作,后文有说明)


打印的应该是ASCII码,不过看起来,ASCII码中确实不是所有的字符都是可打印的。


debug1:
看起来直接发送数据不太现实(或者我没了解到),所以试着用字符串来实现收发:

1、关于这里的字符串结尾,似乎必须写成\r\n才能实现换行,单独的\n不行
2、在轮询发送data字符串的时候,有一个2ms的延时,如果这个延时没有的话,运行效果是这个样子的:


3、所以讲道理除了延时应该还有一个办法:等待发送完成。故代码应该可以改成如下内容:


bug修复!


Code1:
#include "stm32f4xx.h"
#include "delay.h"
void Usart_Init()
{
GPIO_InitTypeDef GPIO_Struct;
USART_InitTypeDef USART_Struct;
NVIC_InitTypeDef NVIC_Struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //初始化USART时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //初始化GPIO时钟
//开启GPIO复用
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_Struct.GPIO_Mode = GPIO_Mode_AF;
GPIO_Struct.GPIO_OType = GPIO_OType_PP;
GPIO_Struct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;
GPIO_Struct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
//初始化GPIO
GPIO_Init(GPIOA,&GPIO_Struct);
USART_Struct.USART_BaudRate = 9600;
USART_Struct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;
USART_Struct.USART_Parity = USART_Parity_No;
USART_Struct.USART_StopBits = USART_StopBits_1;
USART_Struct.USART_WordLength = USART_WordLength_8b;
USART_Struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&USART_Struct);
USART_Cmd(USART1,ENABLE); //咋那么多使能2333
NVIC_Struct.NVIC_IRQChannel = USART1_IRQn;
NVIC_Struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Struct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Struct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_Struct); // 初始化中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //配置串口中断标志位(接收触发)
}
//串口1 中断服务函数
void USART1_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART1,USART_IT_RXNE))
{
res = USART_ReceiveData(USART1);
USART_SendData(USART1,res);
}
}
//字符串长度获取函数
int len(char * strdata)
{
char * point = strdata;
int counter = 0;
while(*point!='\0')
{
counter++;
point++;
}
return counter;
}
//主函数
int main()
{
char *data="Please Input Something\r\n";
char *point;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(168); //延时初始化
Usart_Init();
while(1)
{
point = data;
delay_ms(1000);
while(*point != data[len(data)])
{
USART_SendData(USART1,*point);
point++;
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
}
}
}
运行结果如下所示:

然后既然初始化了中断,我们就稍微皮一下。

发送数据,周期设置为1ms,看看能不能破坏轮询中向PC端发送的数据。
实验结果:

这里应该有两次STM32发送给PC的信息"Please Input Something\r\n",第一次应该是丢失了'P'、"\r\n";第二次应该是丢失了'P' 't' 两个字符。
也就是说,如果中断和轮询都要用到串口的话,是可能产生两个发送信息交叉影响的结果的。所以如果之后用到遥控器或者别的通讯、特别是用同一个串口(或者别的信道)的时候,小心一点。

#MARK一下,这里我打算之后试一试在中断服务函数中判断SR寄存器,看看能不能做到两个发送信息不冲突(起码,不要丢失字符,哪怕两条信息混在一起呢)
今天先不写了,先去啃PWM的内容233333 。
