stm32c8t6串口

链接:https://pan.baidu.com/s/1eFhcnmv37iYll_UsWgx8Rw?pwd=8888 
提取码:8888

自学stm32以来 坑踩了不少,今天分享一下自己的学习笔记

硬件部分  usb转ttl串口一个   stm32f103c8t6一个  杜邦线若干 0.96 4针 oled一个   stlinkv2一个

软件部分  Keil5  串口助手任意  要是没有链接里有

本文推荐阅读人群:刚刚入门stm32的人  标准库学习stm32的人

特别鸣谢 江科大自动协(b站)

正文

串口通信是最基本最简单的通信方式,笔者曾经做过OpenMv识别数字的题目,通过串口发送将识别到的数据传输到小车主控里面,不过调的都是大佬写好的串口收发模块,直到今天才算理解透彻了串口到底是怎么样发送与接受的。

tx与rx是怎样收发的呢  高低电平,如何产生高低电平是硬件的事情,本文略过,基于标准库操作寄存器去完成程序才是本文的要点。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"
//PB 1和12是按键   你要实在没有按键,额 搞一根杜邦线  一端接PB1一端接GND

///*引脚配置*/
//#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)(x))
//#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)(x))
//特别声明 此代码出自江科大自动协  本代码更改仅做学习笔记使用   兜先生于20230625
//正文;请优先看Serial.c里的内容

//#include "LED.h"

特别注意 没有独立按键就算了,插根杜邦线也可以,一端接地,另一端拿手里,想什么时候插就插进去然后拔出来,软件会扫描的。

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

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, ...);

void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);

#endif

 这是串口1的头文件,

#ifndef __SERIAL_H
#define __SERIAL_H

#endif
这三句话是标准的头文件写法  如果害怕代码里有重名可以这么写,然后其他的语句都是函数的声明部分,主函数头文件里只需要#include “seril.h”就可以把所有函数在主函数里面使用了

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//串口1的时钟在APB2总线上  先挂载时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//标准使能A口总线时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;  //GPIO   的结构体声明 可以引出后续的几个参数  Mode Pin  Speed
	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初始化函数
	
	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;  //串口初始化结构体  选模式与GPIO差不多
	USART_InitStructure.USART_BaudRate = 9600;//波特率  实际上另一种写法是  
	                                          //申请一个参数 直接将参数赋给它  然后主函数初始化  usart_init(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; //停止1字节  基本上大家都是1字节 当然看你想要啥
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位字长
	USART_Init(USART1, &USART_InitStructure);//赋值
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//这个是中断法接受数据  一般来讲程序比较小你用查表法也可以 
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级分组分两组  00 01 10  11
	
	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);
}

串口初始化函数,串口收发信息有两种方式一种是查表法,也就是主函数循环里不断查询寄存器的标志位,如果标志位变化了,那么进行xxxx操作,另一种就是中断了,不影响浪费系统时钟,客户已去做其他事然后有数据再管传进来的数据,很方便,学透彻了对以后的操作系统学习有好处。

NVIC是管理分组的,有优先级的区分,同优先级看处理优先级,同处理只能看接口号了,或者说谁来的早,谁先,中断嵌套的具体知识大家肯定清楚。

实际上对c8t6来说只有三个串口PA9 10  2 3 PB 10  11 学会一个其他就差不太多了

void Serial_SendByte(uint8_t Byte) //单字节  也就是1byte  
{
	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]);
	}
}

其实上边的发送一个字节是最关键的,剩下的什么字符串啊,数组啊,说到底不也是循环使用发送一个字节,那我们着重说一下发送一个字节,发送一个字节,当寄存器发送完成会物理也就是硬件置1 那么我们判断发送完成就去监听这个1就OK了,while循环一下。等他置1 跳出循环。

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
//注释人  兜先生  错误疏漏恳请指正   stlinkv2编译

//
uint8_t Serial_TxPacket[4];				//FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//串口1的时钟在APB2总线上  先挂载时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//标准使能A口总线时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;  //GPIO   的结构体声明 可以引出后续的几个参数  Mode Pin  Speed
	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初始化函数
	
	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;  //串口初始化结构体  选模式与GPIO差不多
	USART_InitStructure.USART_BaudRate = 9600;//波特率  实际上另一种写法是  
	                                          //申请一个参数 直接将参数赋给它  然后主函数初始化  usart_init(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; //停止1字节  基本上大家都是1字节 当然看你想要啥
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位字长
	USART_Init(USART1, &USART_InitStructure);//赋值
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//这个是中断法接受数据  一般来讲程序比较小你用查表法也可以 
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级分组分两组  00 01 10  11
	
	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);
}
//实际上你要想改  这个函数随便改  USART_SendData这个才是最底层的函数
//然后对具体的硬件寄存器来讲  你只要发送完成我也将发送完成的标志位1了随便你怎么写
void Serial_SendByte(uint8_t Byte) //单字节  也就是1byte  
{
	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]);
	}
}


//这是对 数字进行按位取出的操作   比如1234%10=4;/10再取余   一样的
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;//1234   就是100*10+20*10+3*10+0*10+4
}
//发送一个十进制的  数据
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');//数是从左往右看 所以是大数先出
	}
}
//重定向printf   要选择USE MIcolib选项 然后引入stdio.h
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}
//重定向sprintf  
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);
}

//按照标准数据帧发送  例如 你在发送区写 ff12345678fe   其他也可以  看你想要什么就改什么  改数组长度也行 别忘记修改括号里的4就行
void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);//帧头
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);//帧尾
}


//转移标志位
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

void USART1_IRQHandler(void)//
//串口一中断服务函数 其目的是将按照首个接受字节为0xff字长大于4的有效数据转存至数组中
//后续可对这个数组值进行操作,这个串口传来的可以是任意符合标准的  串口数据 是串口通信的重要组成部分
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//监听标志位
	{
		uint8_t RxData = USART_ReceiveData(USART1);//将单字节  转存用以判断传输数据开始
		
		if (RxState == 0)//0 1 2  三种状态  用以防止数据传输错误或者编程思路失误
		{
			if (RxData == 0xFF)//如果接受的首个数据是  按照你的传输协议来的数据那么进入状态二存储
			{
				RxState = 1;
				pRxPacket = 0;//每次调用到这里的时候会对下一个所用的数组的可变变量清零  防止疏漏
			}
		}
		else if (RxState == 1)//状态一
		{
			Serial_RxPacket[pRxPacket] = RxData;//存储
			pRxPacket ++;
			if (pRxPacket >= 4)//一般来讲等于4就会触发  但是为了防止意外还是大于等于比较好
			{
				RxState = 2;
			}
		}
		else if (RxState == 2)
		{
			if (RxData == 0xFE)
			{
				RxState = 0;//状态标志位清零
				Serial_RxFlag = 1;//此时  一个接受过程完成  转到uint8_t Serial_GetRxFlag(void)函数进行  标志位的转移
				                  //接上,不过私以为直接申请全局变量这边置1 主函数直接就接到数据更方便
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);//相对应的寄存器标志位清零
	}
}

我一次性全发了吧,想要直接移植的.c  .h都有了  快去试试吧,想要继续了解的可以去B站搜索江科大自动协。

感谢您的观看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值