江协科技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才执行接收,否则就是发的太快了,还没处理完,就跳过这个数据包。

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

运行结果

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

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

相关推荐
PegasusYu2 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
文弱书生6566 小时前
输出比较简介
stm32
黑客呀9 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全
小A1599 小时前
STM32完全学习——使用SysTick精确延时(阻塞式)
stm32·嵌入式硬件·学习
楚灵魈9 小时前
[STM32]从零开始的STM32 HAL库环境搭建
stm32·单片机·嵌入式硬件
小A1599 小时前
STM32完全学习——使用标准库点亮LED
stm32·嵌入式硬件·学习
code_snow11 小时前
STM32--JLINK使用、下载问题记录
stm32·单片机·嵌入式硬件
youcans_13 小时前
【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
stm32·单片机·嵌入式硬件·电机控制·foc
YuCaiH17 小时前
【STM32】MPU6050简介
笔记·stm32·单片机·嵌入式硬件
linux_carlos1 天前
#lwIP 的 Raw API 使用指南
stm32·单片机·mcu·物联网·rtdbs