STM32使用USART发送数据包指令点亮板载LED灯

电路连接:        

        连接显示屏模块,显示屏的SCL在B10,SDA在B11。

程序目的:

        发送@LED_ON指令打开板载LED灯,发送@LED_OFF关闭板载LED灯,与上一个博客不同,这个实际上是实现串口收发文本数据包。

开始编程:

Serial.c

初始化GPIO与中断

  • 初始化A9引脚,设置为复用推挽输出,也就是让内部硬件控制引脚
  • 初始化A10引脚,设置为浮空输入或上拉输入,这里使用上拉输入,具有较好的抗干扰能力
  • 不使用硬件流控制,也就是不使用RTS,CTS等
  • 串口模式为TX|RX(Transform)|(Receive)表示发送和接收
  • 无校验位,可选择奇校验,偶校验等
  • 1位停止位,可选择0.5 1 1.5 2这几个
  • 8字长,不需要校验选8位,需要选9位
  • 开启RXNE(RX No Empty)到NVIC的输出,也就是开启中断
  • 配置中断
void Serial_Init() {
	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;//硬件流控制(不使用,CTS,CTS&RTS)
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
	USART_Init(USART1, &USART_InitStructure);
	//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
	
	//开启中断
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
	
	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);//开启USART
}

中断函数:

状态机如图:

        这里的中断函数与HEX数据包不同,当收到@字符时转为第一个状态,接收数据,由于这个数据不是固定包长,那么收到\r就进入状态2,再收到\n表示接收完成,进入状态0。这里如果包尾不是两个字符的话,只需要设置两个状态即可。

        由于是字符串,因此在状态2转移到状态0时,需要加上字符串的自带的'\0',这样才能定义字符串跟接受到的字符串比较。

        还需要建立两个全局变量,char Serial_RxPacket[100];uint8_t Serial_RxFlag;一个是存放接受的数据,一个是存放接收数据标志位。

        在中断函数中,定义两个静态变量,类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用,这两个静态变量:static uint8_t RxState = 0;static uint8_t pRxPacket = 0;一个用于定位状态,一个用于定位接收到的数据。

中断函数代码:

char Serial_RxPacket[100];

uint8_t Serial_RxFlag;
void USART1_IRQHandler() {
	static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用
	static uint8_t pRxPacket = 0;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		//如果读取DR就自动清除标志位,如果没有就需要手动清除
		uint8_t RxData = USART_ReceiveData(USART1);
		if(RxState == 0){
		//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句
			if(RxData == '@') {
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if(RxState == 1) {
			if(RxData == '\r'){
				RxState = 2;
			}
			else {
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
			}
		}
		else if(RxState ==  2){
			if(RxData == '\n') {
				RxState = 0;
				Serial_RxFlag = 1;
				Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString
			}
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

Serial.c整体代码

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
char Serial_RxPacket[100];

uint8_t Serial_RxFlag;

void Serial_Init() {
	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;//硬件流控制(不使用,CTS,CTS&RTS)
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
	USART_Init(USART1, &USART_InitStructure);
	//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
	
	//开启中断
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
	
	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);//开启USART
}
void Serial_SendByte(uint8_t Byte) {
	USART_SendData(USART1, Byte);//发送数据
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {//等待发送寄存器空,
		//TXE就是发送寄存器空的标志位,不需要手动清零,下一次发送数据时候会自动清零
	}
}
void Serial_SendArray(uint8_t *Array, uint16_t Length){
	uint16_t i;
	for(int i = 0; i < Length; i++) {
		Serial_SendByte(Array[i]);
	}

}
void Serial_SendString(char *Str) {//字符串自带结束标志位
	uint8_t i;
	for(int i = 0; Str[i] != '\0'; i++) {
		Serial_SendByte(Str[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(int 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);//重定向到串口,使得Printf打印到串口
	return ch;

}
//使用sprintf让其他的串口也能使用,sprintf可以把格式化字符输出到一个字符串里
void Serial_Printf(char* format,...){//三个点用来接收后面可变参数列表
	char String[100];
	va_list arg;
	va_start(arg, format);//从format位置开始接收参数表,放在arg里面
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}
uint8_t Serial_GetRxFlag() {
	if(Serial_RxFlag == 1){
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}
void Serial_SendPacket(){

}
void USART1_IRQHandler() {
	static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用
	static uint8_t pRxPacket = 0;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		//如果读取DR就自动清除标志位,如果没有就需要手动清除
		uint8_t RxData = USART_ReceiveData(USART1);
		if(RxState == 0){
		//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句
			if(RxData == '@') {
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if(RxState == 1) {
			if(RxData == '\r'){
				RxState = 2;
			}
			else {
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
			}
		}
		else if(RxState ==  2){
			if(RxData == '\n') {
				RxState = 0;
				Serial_RxFlag = 1;
				Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString
			}
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

Serial.h

源代码:

#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
extern char Serial_RxPacket[];

void Serial_Init();
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();


#endif

GpioControl.c:

        编写GPIO控制函数,封装GPIO引脚的初始化和控制功能。

#include "stm32f10x.h"                  // Device header
void GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode){
	uint32_t RCC_APB2Periph_GPIOx;
	if(GPIOx == GPIOA) {
		RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOA;
	}
	else if(GPIOx == GPIOB) {
		RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOB;
	}
	else if(GPIOx == GPIOC) {
		RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOC;
	}
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);//ctrl + Alt + 空格:可以出现代码提示
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GpioMode;//推挽输出
	GPIO_InitStructure.GPIO_Pin = Pin;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOx, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOx, Pin);
}
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN) {//反转当前引脚状态
	if(GPIO_ReadOutputDataBit(GPIOx,GPIO_PIN) == 0){
		GPIO_SetBits(GPIOx,GPIO_PIN);
	}
	else{
		GPIO_ResetBits(GPIOx, GPIO_PIN);
	}
}
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign) {//控制引脚
	if(sign == ENABLE){
		GPIO_SetBits(GPIOx, GPIO_PIN);
	}
	if(sign == DISABLE){
		GPIO_ResetBits(GPIOx, GPIO_PIN);
	}
}

GpioControl.h:

#ifndef __GPIOCONTROL_H
#define __GPIOCONTROL_H

void GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode);
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN);
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign);

#endif 

main.c

        在main函数中,主要逻辑就是判断标志位来得到是否有数据接收,若有则跟指令进行对比,如果是打开灯指令,那么就置C13引脚为低电平并发送LED_ON_OK指令,点亮LED灯。若是关灯指令,那么就置引脚为高电平,关闭LED灯并发送LED_OFF_OK,若都不是,那么就输出ERROR_CMD指令表示指令错误。

主要代码如下:

#include "stm32f10x.h"                  // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
#include "GpioControl.h"
#include <string.h>
uint8_t RxData;
uint8_t KeyNum;

int main() {
	GpioInit(GPIOC, GPIO_Pin_13, GPIO_Mode_Out_PP);
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1, 1, "TxData:");
	OLED_ShowString(3, 1, "RxData:");

	while(1){
		if(Serial_GetRxFlag() == 1) {
			OLED_ShowString(4,1, "                ");//清除第四行
			OLED_ShowString(4,1, Serial_RxPacket);
			if(strcmp(Serial_RxPacket,  "LED_ON") == 0) {
				GpioControl(GPIOC, GPIO_Pin_13, DISABLE);
				Serial_SendString("LED_ON_OK\r\n");
				OLED_ShowString(2,1,"                ");
				OLED_ShowString(2,1,"LED_ON_OK");
			}
			else if(strcmp(Serial_RxPacket,  "LED_OFF") == 0) {
				GpioControl(GPIOC, GPIO_Pin_13, ENABLE);
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2,1,"LED_OFF_OK");
			}
			else {
				Serial_SendString("ERROR_CMD\r\n");
				OLED_ShowString(2,1,"                ");
				OLED_ShowString(2,1,"ERROR_CMD");
			}
		}
	}
}

程序现象:

 

程序及软件下载:

程序打包代码:程序包下载

串口助手下载:串口助手下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值