【stm32】USART编码部分--详细步骤

USART编码部分(文章最后附上源码)

如果看不懂步骤可以根据源码参考此篇文章就能轻而易举学会USART通信啦!

编码步骤

第一步 开启时钟

把需要用到的USART和GPIO的时钟打开

第二部 GPIO初始化

把TX配置成复用输出,RX配置成输入(上拉输入、浮空输入)。

第三步 配置USART初始化

一个结构体配置所有参数

第四步 发送或接收

只需发送功能
  • 就直接开启USART,初始化就结束了
关于发送数据的类型
  1. 首先写发送一个字节数据的函数 SendByte函数

    1. 调用函数USART_SendData

    2. 等待发送寄存器空标志位

  2. 发送数组SendArray的函数

    1. 函数名形参一个字符指针,长度
    2. 函数体内调用SendByte,一位一位的发送数组数据
  3. 发送字符串 SendString 的函数

    1. 形参为一个字符指针

    2. 函数体内使用for循环或while一位一位的发送字符串的每个字节,直到遇到\0停止

  4. 发送数字SendNumber的函数

    1. 形参一个数字,类型给32位,然后还有一个长度

    2. 在函数里面需要把Number的十位个位百位等,以十进制拆分开,然后转换成字符数字对应的数据,一次发送出去

    3. 比如12345, 取万位就是12345/10000%10得到万位

    4. 需要先写一个次方函数, 形参是一个X,一个y,返回值是X的Y次方,都是32位

    5. 回到SendNumber,也是每次发送数据的每一位这个逻辑

需要接收功能
  • 首先配置PA10为上拉输入或者浮空输入

  • 接着在串口初始化里配置接收模式

  • 可以使用查询和中断两种方法

  • 如果使用查询,那初始化就结束了

    • 查询的流程是:

    • 在主函数里不断判断RXNE标志位,如果置1了(if成立),就说明收到数据了

    • 再调用ReceiveData,读取DR寄存器,就ok了

    • 最后还有清除标志位的问题,根据参考手册的寄存器描述进行相应的判断,是否需要清除标志位

  • 如果使用中断,还需要在USART_cmd之前开去中断,配置NVIC,那就在开启USART之前,再加上ITConfig和NVIC的代码就行了

    • 接着写中断函数,在启动文件查找函数名

    • 中断函数里判断接收寄存器非空标志位

接收数据步骤(中断函数建立之后)
  1. 定义一个接收数据的变量和一个接受变量的标志位

  2. 建立一个接收数据标志位自动请0的函数,函数里清零标志位,返回1

  3. 建立一个返回数据的函数 GetRxData 的函数,把接收到了数据返回

    1. 上面两部也可以通过把两个变量声明为外部可调用的全局变量
  4. 中断函数里引用接收数据函数,赋给接收数据的变量,置标志位为1,证明接收到了数据

  5. 主函数里判断标志位,如果标志位为1,证明接收到了数据

  6. 可以在判断函数里使用OLED显示串口接收到的数据,然后把这个数据使用串口发送函数再发送到电脑串口助手进行显示

初始化之后

  • 初始化之后,如果要发送数据,调用一个发送函数就行了

  • 如果要接收数据,就调用接收的函数

  • 如果要获取发送和接收的状态,就调用获取标志位的函数

USART 函数介绍

  • USART_ClockInit 和 USART_ClockStrustInit 用来配置同步时钟输出的,包括时钟是不是要输出,时钟的极性相位等参数

  • USART_DMACmd 可以开启USART到DMA的触发通道

  • USART_SendData 发送数据

  • USART_ReceiveData 接收数据

    复制代码
      *发送和接收的时候用*

关于子函数

传递字符串

由于字符串自带一个结束标志为,所以就不需要传递长度参数;

for(i=0; String[i] != '\0'; i++)

换行:Serial_SendString("\r\n")

传递数字

  • 加一个偏移

  • 首先定义一个取数字模的函数Serial_Pow

  • Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);//0x30可以写成'0'

Printf函数一直方法使用

  1. 使用Printf之前,打开工程选项,勾选Use MicroLIB(是Keil为嵌入式平台优化的一个精简库)

  2. 要用的Printf函数就可以用MicroLIB

  3. 对Printf进行重定向,将Printf打印的东西输出到串口,因为printf函数默认输出到屏幕,单片机没有屏幕,所以要进行重定向。

printf使用步骤

  1. 最开始加上,#include<stdio.h>

  2. 在最后重写fputc函数。 int fputc(int ch, FILE *F) 这是fputc函数的原型

  3. 然后在函数里面把fputc重定向到串口 (Serial_SendByte(ch));

  4. return ch;

  5. 这样printf函数就移植好了

  6. 最后在串口头文件中包含#include<stdio.h>,相当于main函数内也包含stdio.h

fputc 与 Printf的关系

  • 因为fputc是pritf函数的底层

  • pritf函数在打印的时候,就是不断调用fputc函数一个个打印的

  • 我们把fputc函数重定向到了串口,那printf自然就输出到了串口

printf函数在主函数中使用方法

printf("Num=%d\r\n",666);

如果多个串口都想用Printf的方法

这时就可以用Spritf

  • Spritf可以把格式化字符输出到,一个字符串里
  1. 先定义一个字符串(主函数里) char string[100]

  2. 然后sprintf第一个参数是打印输出的位置, sprintf(string, "Num=%d\r\n",666);

  3. 目前这个格式化的字符在String里

  4. 接着Serial_SendString

  • sprintf可以设置打印位置,不涉及重定向

  • 所以每个串口都有可以使用Sprintf进行打印

封装Sprintf

  • 由于printf这类函数比较特殊,它支持可变的参数
  1. 在串口模块里添加头文件 #include <stdarg.h>

  2. 然后在最后对printf函数进行封装 void Serial_Printf(char *format, ...) format这个参数用来接收格式化字符串 ...三个点用来接收后面的可变参数列表

  3. 在函数里面

    1. 首先定义输出的字符串 char string[100]

    2. va_list arg 定义一个参数列表变量

    3. va_start(arg, format) 从format位置开始接收参数表,放在arg里面

    4. 之后 vsprintf(string, format, arg); 对于这种封装格式要用vsprintf

    5. va_end(arg) 释放参数表

    6. 最后是 Serial_SendString(String) 把String发送出去

关于乱码

Serial.c文件程序//也就是串口的.c文件

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

Serial.h文件程序//也就是串口的.h文件

cpp 复制代码
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

main.c文件程序

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void)
{
	OLED_Init();
	OLED_ShowString(1, 1, "RxData:");
	
	Serial_Init();
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
}

感谢各位能坚持看到这里!

如果能有机会得到您的一个小赞那我就更有动力了!

相关推荐
-Springer-7 小时前
STM32 学习 —— 个人学习笔记5(EXTI 外部中断 & 对射式红外传感器及旋转编码器计数)
笔记·stm32·学习
LS_learner7 小时前
树莓派(ARM64 架构)Ubuntu 24.04 (Noble) 系统 `apt update` 报错解决方案
嵌入式硬件
来自晴朗的明天7 小时前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT8 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠8 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
代码游侠19 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
xuxg200521 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT1 天前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen1 天前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制