今天这一节教程作为stm32入门教程讲解
从硬件分析、CUBEMX初始化、 敲代码、代码分析,来领着大家对STM32的项目过程有个大致的了解以及养成建立一套规范化的工程文件架构的习惯
废话不多说,上干货
这里先不用了解太多底层的,因为我们对32的程序上体现的控制基本上已经被封装的很完美了,这里推荐用HAL库。简直就像Arduino一样的简单,所以说,入门32其实不难,但是学通32还是很难的。所以要加油了!
基本的硬件介绍就在笔记中了,看不懂么得关系,咱们先看更高一层的。
.
.
.
在RCC里面的HSE配置的是晶振时钟,配置完成后我们会看到两个相关管脚变成了绿色,说明已初始化。
复用,即平时的时候是IO口,但是当我们有特殊用途的时候,比如说UART通信(异步全双工串口通信)PA9可以作为TX,PA10可作为RX使用。直接在我们的右面的可视框图里面点击相应的引脚配置就OK。
但是配置完之后还是黄色的,不是绿色的,代表我们还没有完全的配置完毕,不要着急,请看下一步。
这一块内容就是波特率的设置,一般系统自动按默认值11520bps,不过我们可以修改;默认的码元是8N1型的,8N1是什么,请看我的另一篇文章:有关单片机串口通信的原理性问题讲解
起个自己想要的名字,配置下路径,配置下想要生成的代码适用的编译器,这里选择MDK-ARM
在这里勾选上那个,这样生成的代码会将不同硬件初始化的代码分开成不同的c文件,不集中在一个c文件里面,这样就更加方便的去调用和更改啥的
至此,代码初始化的事儿已经全部完成,我们在这里面并没有接触到寄存器啥的,因为这个mx已经全部封装好了,这样能让我们更加专注的写逻辑代码,而不用再去考虑太多底层配置的问题。
不过,作为一名合格的优秀的嵌入式工程师,还是要熟练地掌握底层的应用配置,这样能让我们更好的去理解一些问题。
.
.
.
这就是CUBE已经帮我们初始好的代码,现在,下一步,我就给大家讲一下,一个好的编程文件架构应该是什么样的。
.
.
.
按图上顺序操作就OK 了
第6步,找到我们刚才在src文件中建立的那个文件夹main_app,因为我们的main_app.h文件在这里面,这样就不会有错误和警告了
那么大家就会有个问题,为什么我要这么做呢,为什么不直接在main.c文件里面去写我们想要的程序呀
这里之后再解释,大家先把这个疑惑保留下。
.
.
.
#include "main_app.h"
#include
#define Uart_Timeout 0xFFFF //定义一个超时参数,超时时间设为0XFFFFms
void Uart1_Send(uint8_t *buf,uint32_t size);//声明下我们下面定义的一个字符串发送函数
int fputc(int data, FILE *f)//改造下fputc函数,将fputc函数的接口改到我们的这个串口上,因为printf里面调用的有fputc,所以到时候我们就可以直接用printf来发送串口数据
{
Uart1_Send((uint8_t *)&data,1);//调用下我们刚才声明的串口发送函数
}
//串口输出
void Uart1_Send(uint8_t *buf,uint32_t size)//定义一个串口数据发送函数
{
HAL_UART_Transmit(&huart1, buf, size, Uart_Timeout);//将我们的参数传给我们这个hal库中本身存在的一个串口发送函数,从而让这个函数更加简单
}
void Uart1_SendTest(void)//串口发送数据的测试
{
Uart1_Send("hello,world",11);//发送一个11位的数据
}
void Main_App(void)//定义这个函数中的最重要的函数,类似于主程序中的main函数,不过这个只是我们起的名字,main函数一个工程中只能有一个
{
Uart1_SendTest();
printf("世界你好\r\n");
while(1)
{
}
}
.
.
.
说起代码的疑难杂症,根源问题是因为我们对hal库不熟而导致的,所以就带大家通过代码的分析来初步认识下几个hal库的函数定义
HAL_UART_Transmit(&huart1, buf, size, Uart_Timeout);
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint16_t *tmp;
uint32_t tickstart = 0U;
/* Check that a Tx process is not already ongoing */
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;
/* Init tickstart for timeout managment */
tickstart = HAL_GetTick();
huart->TxXferSize = Size;
huart->TxXferCount = Size;
while (huart->TxXferCount > 0U)
{
huart->TxXferCount--;
if (huart->Init.WordLength == UART_WORDLENGTH_9B)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
tmp = (uint16_t *) pData;
huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
if (huart->Init.Parity == UART_PARITY_NONE)
{
pData += 2U;
}
else
{
pData += 1U;
}
}
else
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
}
}
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
emmmmm,具体的代码就先不带大家分析了,在这里说下几个参数的作用以及我们应该如何设置就OK
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
UART_HandleTypeDef *huart:一个结构体的句柄,不用理解是什么,我们要用的时候直接对其取地址就ok
uint8_t *pData:指针型的数据
uint16_t Size:数据的大小
uint32_t Timeout:超时时间设置
void Uart1_Send(uint8_t *buf,uint32_t size)//定义一个串口数据发送函数
{
HAL_UART_Transmit(&huart1, buf, size, Uart_Timeout);//将我们的参数传给我们这个hal库中本身存在的一个串口发送函数,从而让这个函数更加简单
}
这就是我们最后改造好的函数,就是根据上面的要求去传入的参数,只不过把参数简化成了两个,把函数名字简化了并且更加具象化了下。
这就实现了我们一个数据的发送
这就是我们应用到的fputc的地方,我们先找到这个函数的原型
int fputc(int data, FILE *f)//改造下fputc函数,将fputc函数的接口改到我们的这个串口上,因为printf里面调用的有fputc,所以到时候我们就可以直接用printf来发送串口数据
{
Uart1_Send((uint8_t *)&data,1);//调用下我们刚才声明的串口发送函数
}
printf("世界你好\r\n");
fputc函数原型
在标准库stdio.h里面放着
extern _ARMABI int fputc(int /*c*/, FILE * /*stream*/) __attribute__((__nonnull__(2)));
/*
* writes the character specified by c (converted to an unsigned char) to
* the output stream pointed to by stream, at the position indicated by the
* asociated file position indicator (if defined), and advances the
* indicator appropriately. If the file position indicator is not defined,
* the character is appended to the output stream.
* Returns: the character written. If a write error occurs, the error
* indicator is set and fputc returns EOF.
• 将 c 指定的字符(转换为无符号字符)写入
• 按流指向的输出流,位置由
• 已确定的文件位置指示器(如果定义),并推进
• 适当指示器。如果未定义文件位置指示器,
• 该字符追加到输出流中。
* 返回:写入的字符。如果发生写入错误,则错误
• 指标设置,fputc 返回 EOF。
*/
然后我们改造成这样
int fputc(int data, FILE *f)//改造下fputc函数,将fputc函数的接口改到我们的这个串口上,因为printf里面调用的有fputc,所以到时候我们就可以直接用printf来发送串口数据
{
Uart1_Send((uint8_t *)&data,1);//调用下我们刚才声明的串口发送函数
}
因为我们知道printf函数里面调用的是fputc函数,所以我们这样改造fputc函数的接口后,就直接可以用printf函数向串口发送数据了
这就是我们本期的教程了,学习32是个循序渐进的过程,没有捷径,多看多写多理解,就OK
如果这篇文章对你有帮助,记得点赞哦,如果文章哪里有错误,请在评论区里及时指出。谢各位大佬~