江协科技STM32学习- P29 实验- 串口收发HEX数据包/文本数据包

🚀write in front🚀

🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚

🚀Projeet source code🚀

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

【STM32】江科大STM32学习笔记汇总(已完结)_stm32江科大笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

stm32学习笔记-作者: Vera工程师养成记

stem32江科大自学笔记-CSDN博客

术语:

|--------------------------------------|---------------------------------------------|
| 英文缩写 | 描述 |
| GPIO:General Purpose Input Onuput | 通用输入输出 |
| AFIO:Alternate Function Input Output | 复用输入输出 |
| AO:Analog Output | 模拟输出 |
| DO:Digital Output | 数字输出 |
| 内部时钟源 CK_INT:Clock Internal | 内部时钟源 |
| 外部时钟源 ETR:External Trigger | 时钟源 External 触发 |
| 外部时钟源 ETR:External Trigger mode 1 | 外部时钟源 External 触发 时钟模式1 |
| 外部时钟源 ETR:External Trigger mode 2 | 外部时钟源 External 触发 时钟模式2 |
| 外部时钟源 ITRx:Internal Trigger inputs | 外部时钟源,ITRx (Internal trigger inputs)内部触发输入 |
| 外部时钟源 TIx:exTernal Input pin | 外部时钟源 TIx (external input pin)外部输入引脚 |
| CCR:Capture/Comapre Register | 捕获/比较寄存器 |
| OC:Output Compare | 输出比较 |
| IC:Input Capture | 输入捕获 |
| TI1FP1:TI1 Filter Polarity 1 | Extern Input 1 Filter Polarity 1,外部输入1滤波极性1 |
| TI1FP2:TI1 Filter Polarity 2 | Extern Input 1 Filter Polarity 2,外部输入1滤波极性2 |
| DMA:Direct Memory Access | 直接存储器存取 |

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。

本节我们来写一下串口收发数据包的代码

1.🚚第一个代码:串口收发HEX数据包

接线图

和上节的接线基本一样,只是在PB1口接了一个按键,用于控制。

我们在上节的Serial.c文件里加上HEX收发数据包的部分,定义的格式就和上一篇博客中说的HEX数据包接收固定包长,含包头包尾的这个思路一样,就按这个写状态机的思路写。

为了收发数据包,我们先定义两个缓冲区的数组。

这四个数据只存储发送或接收的载荷数据,包头包尾就不存了。

初始化的代码都不需要更改,把上节的这个函数删掉

然后我们先写一个send packet的函数。我们想要的效果是调用一下这个函数,Tx packet数组的四个数据就会自动加上包头报尾发送出去。

cpp 复制代码
void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArry(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}

实验结果:

接下来我们就来写一下接收数据包的代码

首先在接收中断函数里,我们就需要用状态机来执行接收逻辑了,这就按上面那个状态转移图来写。

首先我们要定义一个标志当前状态的变量s,在中断这里,我们可以在函数里面定义一个静态变量RxState当成这个状态变量s

然后根RxState的不同,需要进入不同的处理程序。

📤️📤️注意:这里一定是要用else if,如果只用3个并列的If,可能在状态转移的时候会出现问题,比如在状态0,你想转移的状态1就置RxState等于1,结果就会造成下面状态1的条件就立马满足了,这样会出现连续两个if都同时成立的情况,这个情况我们不希望出现。

🎵🎵🎵

所以这里使用else if,保证每次进来之后,只能选择执行其中一个状态的代码,或者你用switch case语句,也可以保证只有一个条件满足。

这就是状态选择的部分。然后就依次写每个状态执行的操作和状态转移条件就行了。

🔊🔊🔊这个程序还隐藏有一个问题,就是这个Serial_RxPacket数组,它是一个同时被写入,又同时被读出的数组。

🔊🔊🔊在中断函数里,我们会依次写入它,在主函数里,我们又会依次读出它。这会造成数据包之间可能会混在一起 。比如读出的过程太慢了,前面两个数据刚读出来,等了一会儿才继续往后读取。这时后面的数据就有可能会刷新为下一个数据包的数据,也就是读出的数据可能一部分属于上一个数据包,另一部分属于下一个数据包

🔋🔋解决方法可以在接收部分加入判断,就是在每个数据包读取处理完毕后,再接收下一个数据包。当然,很多情况下其实还可以不进行处理,像这种hex数据包多是用于传输各种传感器的每个独立数据,比如陀螺仪的xyz轴数据,温湿度数据等等,它们相邻数据包之间的数据具有连续性,这样即使相邻数据包混在一起了,也没关系。所以这种情况下就不需要关心这个问题,具体到底怎么处理,还需要大家结合实际情况来操作了,这里就提一下这个可能存在的问题。大家了解一下就行了。

我们这个收发hex数据包的程序大概就讲完了。

接下来加上按键部分的代码,现象是按一下按键变换一下数据,发送到串口助手上。

定义一个变量

然后在主函数中实现按键的控制就行

UART.c

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

void UART_Init(void)
{
	
	//RCC使能外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //RCC使能UART时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //RCC使能GPIOA时钟
	
	//配置GPIO
	GPIO_InitTypeDef gpioInitStructure;
	gpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//GPIO为复用推挽输出模式
	gpioInitStructure.GPIO_Pin = GPIO_Pin_9;			//GPIOA_Pin9为UART1_Tx
	gpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpioInitStructure);
	
	gpioInitStructure.GPIO_Mode = GPIO_Mode_IPU;		//GPIO为上拉输入
	gpioInitStructure.GPIO_Pin = GPIO_Pin_10;			//GPIOA_Pin9为UART1_Rx
	gpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpioInitStructure);
	
	//配置UART
	USART_InitTypeDef USART_InitStruct;
	USART_StructInit(&USART_InitStruct);
	
	USART_InitStruct.USART_BaudRate = 9600;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;		//UART使能Tx发送
	USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStruct);
	
	//使能UART中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	//NVIC配置UART中断优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;		//使能UART1_IRQ中断
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct);
	
	
	//使能UART
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t data)
{
	USART_SendData(USART1, data);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待UART TXE标志位
	//写入之后等待TXE标志位置1,表示数据从TDR寄存器转移到了发送移位寄存器
	//如果不等待TDR数据移动到发送移位寄存器,下次写时就会覆盖上一次数据
	//该标志位在写DR寄存器时自动清除
}

void Serial_SendArry(uint8_t data[], uint8_t length)
{
	for(int i=0; i<length; i++)
	{
		Serial_SendByte(data[i]);
	}
}

void Serial_SendString(char *str)
{
	if(str)
	{
		while(*str != '\0')
		{
			Serial_SendByte(*str++);
		}
	}
}

uint32_t Serial_Power(uint32_t x, uint32_t y)
{
	uint32_t ret = 1;
	
	while(y > 0)
	{
		ret *= x;
		y--;
	}

	return ret;
}

void Serial_SendNumber(uint32_t number, uint16_t length)
{
	int i = 0;
	uint8_t data;
	
	for(i=0; i<length; i++)
	{
		data = number/Serial_Power(10, length - i -1)%10 + '0';
		Serial_SendByte(data);
	}
}

/* 重写fputc()函数,重定向printf()到串口
 *
 */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	
	return ch;
}



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

volatile uint8_t Seial_RxFlag = 0;
uint8_t Serial_RxPacket[4];
uint8_t Serial_TxPacket[4];



//中断服务函数
void USART1_IRQHandler()
{
	static uint8_t Uart_State;
	static uint8_t pRxPacket = 0;;
	uint8_t Seial_Data = 0;
	
	Seial_Data = USART_ReceiveData(USART1);
	
	if(Uart_State == 0)
	{
		if(Seial_Data == 0xFF)
		{
			Uart_State = 1;
		}
	}
	else if(Uart_State == 1)
	{
		Serial_RxPacket[pRxPacket] = Seial_Data;
		pRxPacket++;
		if(pRxPacket >= 4)
		{
			pRxPacket = 0;
			Uart_State = 2;
		}
	}
	else if(Uart_State == 2)
	{
		if(Seial_Data == 0xFE)
		{
			Uart_State = 0;
			Seial_RxFlag = 1;
		}
	}
	
	//清除UART RXNE 标志位
	USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}

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

void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArry(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}

man.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>

extern uint16_t Num;


int main(int argc, char *argv[])
{
	OLED_Init();
	OLED_ShowString(1, 1, "UART:");
	OLED_ShowString(1, 1, "           ");
	
	UART_Init();
	
	//printf("Hello World\r\n");
	//OLED_ShowString(2, 1, "RX:");
	
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	Serial_SendPacket();
	
	while(1)                                                                                 
	{
		if(Serial_GetRxFlag() == 1)
		{
			OLED_ShowHexNum(1, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(1, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(1, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(1, 11, Serial_RxPacket[3], 2);
		}
	}
	
	return 1;
}

实验现象:

实验改进2,按下按键收发送数据:

main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>
#include "Key.h"

extern uint16_t Num;


int main(int argc, char *argv[])
{
	OLED_Init();
	Key_Init();
	
	OLED_ShowString(1, 1, "UART:");
	OLED_ShowString(1, 1, "           ");
	
	UART_Init();
	
	//printf("Hello World\r\n");
	//OLED_ShowString(2, 1, "RX:");
	
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	Serial_SendPacket();
	
	uint8_t Key_Num = 0;
	
	while(1)                                                                                 
	{
		
		Key_Num = Key_GetNum();
		if(Key_Num == 1)
		{
			Serial_TxPacket[0]++;
			Serial_TxPacket[1]++;
			Serial_TxPacket[2]++;
			Serial_TxPacket[3]++;
			
			OLED_ShowString(1, 1, "Tx Packet:");
			OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
			OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
			OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
			OLED_ShowHexNum(2, 11, Serial_TxPacket[3], 2);
		}
		
		if(Serial_GetRxFlag() == 1)
		{
			OLED_ShowString(3, 1, "Rx Packet:");
			OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(4, 11, Serial_RxPacket[3], 2);
		}
	}
	
	return 1;
}

实验结果:

2🚚.第二个程序:串口收发文本数据包

接线图:

在PA1口接了一个LED,用于指示。

我们在上一个工程的基础上改,按键部分的代码就不要了。

接下来就按上节讲过的文本数据包接收的思路来写,这里是可变包长,含包头包尾。

我们这里就只写接收的部分,因为发送的话不方便像hex数组一样一个个更改的。所以发送就直接在主函数里sendstring或者printf就行了,非常简单。

所以这个发送数据包的函数就不要了

接收部分我们来来实现一下。

数组的长度给多点,防止溢出,给个100,这要求单条指令最长不能超过一百个字符。

之后是中断的状态机部分,参考上面状态转移图写。

接下来就把LED部分的代码加进来就行

先是判断字符串是不是等于我们规定的指令再执行相应的操作。判断字符串要用到字符串处理函数,要包含头文件#include "string.h"。

在这里我们判断两个字符串是否相等,需要用到strcmp函数,如果不知道这个函数的用法可以去翻一下我的C语言复习专栏,在这篇博文中我详细介绍了字符串处理函数。

🔊🔊还有个问题需要说明,同样还是之前的个问题。如果连续发送数据包程序处理不及时,可能导致数据包错位

🔊🔊在这里,文本数据包,每个数据包是独立的,不存在连续,这如果错位了问题就比较大。所以在程序这里我们可以修改一下,等每次处理完成之后,再开始接收下一个数据包

我们可以这样在中断函数中等待包头的时候再加一个条件,如果数据等于包头并且Serial_RxFlag等于等于0才执行接收,否则就是发的太快了,还没处理完,就跳过这个数据包。

然后上面这个读取标志位之后立刻清零的函数先删掉。

运行结果

这就是我们第二个程序的现象。

本节的内容到这里就结束了,下节继续。

相关推荐
秀秀更健康2 小时前
stm32: 系统时钟如何配置为72Mhz
stm32·单片机·嵌入式硬件
归零鸟7 小时前
WD Elements移动硬盘能识别出盘但不能出盘的修复记录
stm32·单片机·嵌入式硬件
追兮兮9 小时前
MCUQuickStart v1.1.0发布,一键生成Keil工程+RTOS模板
stm32·单片机·嵌入式硬件·freertos·gd32·keil5
rit84324999 小时前
STM32移植NES模拟器指南
stm32·单片机·嵌入式硬件
都在酒里9 小时前
STM32 I2C通信协议详解——标准库函数实现(通讯协议总结一)
stm32·嵌入式硬件·i2c
fengfuyao9859 小时前
STM32 HAL库实现串口DMA接收不定长数据
stm32·单片机·嵌入式硬件
yuan199979 小时前
STM32直流无刷电机六拍方波控制器程序
stm32·单片机·嵌入式硬件
番茄灭世神11 小时前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
2zcode13 小时前
基于STM32的直流电机串级PID伺服控制系统设计与实现
stm32·单片机·嵌入式硬件·直流电机
都在酒里13 小时前
STM32低功耗休眠详解——睡眠、停止与待机模式实战,综合应用(三)
stm32·单片机·嵌入式硬件