STM32F103C8T6/串口输出/printf函数内调fputc接口改造/硬件分析、CUBEMX初始化、 敲代码、代码分析/建立一套规范化的工程文件架构

STM32F103C8T6

今天这一节教程作为stm32入门教程讲解
从硬件分析、CUBEMX初始化、 敲代码、代码分析,来领着大家对STM32的项目过程有个大致的了解以及养成建立一套规范化的工程文件架构的习惯
废话不多说,上干货

一、硬件分析

STM32串口硬件分析笔记(字丑勿喷)
这里先不用了解太多底层的,因为我们对32的程序上体现的控制基本上已经被封装的很完美了,这里推荐用HAL库。简直就像Arduino一样的简单,所以说,入门32其实不难,但是学通32还是很难的。所以要加油了!

基本的硬件介绍就在笔记中了,看不懂么得关系,咱们先看更高一层的。
.
.
.

二、CUBEMX的程序代码初始化

STM32串口硬件分析笔记(字丑勿喷)

1、打开cubemx(官网最新版本的) 在这里插入图片描述
2、输入STM32F103C8

在这里插入图片描述
双击这一块
在这里插入图片描述

3、先配置时钟源

在RCC里面的HSE配置的是晶振时钟,配置完成后我们会看到两个相关管脚变成了绿色,说明已初始化。
在这里插入图片描述

4、配置我们的烧录方式引脚

在这里插入图片描述

5、配置我们的复用引脚

复用,即平时的时候是IO口,但是当我们有特殊用途的时候,比如说UART通信(异步全双工串口通信)PA9可以作为TX,PA10可作为RX使用。直接在我们的右面的可视框图里面点击相应的引脚配置就OK。
但是配置完之后还是黄色的,不是绿色的,代表我们还没有完全的配置完毕,不要着急,请看下一步。
在这里插入图片描述

6、在Connectivity里面的USART1模式里面选择异步全双工通信模式

然后我们引脚就配置好了
在这里插入图片描述

这一块内容就是波特率的设置,一般系统自动按默认值11520bps,不过我们可以修改;默认的码元是8N1型的,8N1是什么,请看我的另一篇文章:有关单片机串口通信的原理性问题讲解
在这里插入图片描述

7、然后在我们的时钟树里面配置

如下图
在这里插入图片描述

8、在Project Manager中进行配置

起个自己想要的名字,配置下路径,配置下想要生成的代码适用的编译器,这里选择MDK-ARM
在这里插入图片描述
在这里勾选上那个,这样生成的代码会将不同硬件初始化的代码分开成不同的c文件,不集中在一个c文件里面,这样就更加方便的去调用和更改啥的
在这里插入图片描述

9、然后就最后一步

在这里插入图片描述
至此,代码初始化的事儿已经全部完成,我们在这里面并没有接触到寄存器啥的,因为这个mx已经全部封装好了,这样能让我们更加专注的写逻辑代码,而不用再去考虑太多底层配置的问题。
不过,作为一名合格的优秀的嵌入式工程师,还是要熟练地掌握底层的应用配置,这样能让我们更好的去理解一些问题。
.
.
.

三、初始化后的代码走马观花

在这里插入图片描述
这就是CUBE已经帮我们初始好的代码,现在,下一步,我就给大家讲一下,一个好的编程文件架构应该是什么样的。
.
.
.

四、养成一个建立良好文件架构的习惯

1、新建一个文件如图

在这里插入图片描述

2、然后按保存按钮,在工程文件目录下找到Src(sorce)这个文件夹,在里面新建里一个文件夹叫做main_app,根据个人习惯吧,我还是比较喜欢把自己要写的东西放到这里面来。然后在把该文件命名成main_app.c一个c文件就建好了

在这里插入图片描述

3、如法炮制,在main_app文件夹里面再建立一个main_app.h文件,一个头文件就建好了

在这里插入图片描述

4、单击三色方框按扭(文件管理按钮),在Group里面新建立一个组名字是main_app,在这个组里面再添加一个文件,是我刚才建立的main_app.c文件,找到文件目录,并add进去。点击ok

在这里插入图片描述
这就是我们建立好之后的文件样子
在这里插入图片描述

5、设置下我们的头文件的调用路径

按图上顺序操作就OK 了
第6步,找到我们刚才在src文件中建立的那个文件夹main_app,因为我们的main_app.h文件在这里面,这样就不会有错误和警告了

在这里插入图片描述

6、在main_app.h里面include几个比较常用到的头文件,然后再把main_app.h头文件include到mian_app.c里面去

在这里插入图片描述
好了,这样我们的文件架构就做好了。

那么大家就会有个问题,为什么我要这么做呢,为什么不直接在main.c文件里面去写我们想要的程序呀
这里之后再解释,大家先把这个疑惑保留下。
.
.
.

五、开始敲写我们今天的代码

#include "main_app.h"
#include <stdio.h>
#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库的函数定义

1、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库中本身存在的一个串口发送函数,从而让这个函数更加简单
}

这就是我们最后改造好的函数,就是根据上面的要求去传入的参数,只不过把参数简化成了两个,把函数名字简化了并且更加具象化了下。
这就实现了我们一个数据的发送

2、对fputc函数的接口改造

这就是我们应用到的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

如果这篇文章对你有帮助,记得点赞哦,如果文章哪里有错误,请在评论区里及时指出。谢各位大佬~

  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用标准库配置stm32f103c8t6串口1并使用printf函数代码示例: ``` #include "stm32f10x.h" #include <stdio.h> void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能USART1和GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置USART1的TX引脚(PA9)为推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1的RX引脚(PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1的工作参数 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE); } int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return ch; } int main(void) { USART1_Init(); printf("Hello, world!\n"); while (1); return 0; } ``` 在该示例中,首先定义了一个USART1_Init函数,用于配置USART1的引脚和工作参数,并使能USART1。然后定义了一个fputc函数,用于将字符发送到USART1,并等待发送完成。最后在main函数中调用了USART1_Init函数,并使用printf函数输出一段字符串。需要注意的是,在使用printf函数之前,必须先调用USART1_Init函数进行初始化

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值