STM32利用串口外设发送数据

今天2024.3.21日上午学习了一下基本的串口初始化,利用串口发送一个字节的数据,看时间也快11点了,上午就学习这么多吧,把上午的知识总结一下,串口初始化的过程:

看着图来编写串口初始化的过程:

//第一步:把USART和GPIO的时钟打开

//第二步:GPIO初始化TX复用输出RX输入

//第三步:初始化USART(9600波特率、8位字长、无校验、1位停止位、无流控,只有发送模式)

//第四步:发送开启USART,接收需要配置中断和NVIC

接着来学习一下串口标准库中的函数吧:

下面就是工程中的Serial.c的文件:

#include "stm32f10x.h"                  // Device header


void Serial_Init(void)
{
	//第一步:把USART和GPIO的时钟打开
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//第二步:GPIO初始化TX复用输出RX输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;        //因为只写发送,所以只初始化TX引脚就好了
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;  //速率50M
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	//第三步:初始化USART(9600波特率、8位字长、无校验、1位停止位、无流控,只有发送模式)
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;            //波特率
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     //硬件流控  不使用
	USART_InitStruct.USART_Mode = USART_Mode_Tx;     //串口模式   发送模式
	USART_InitStruct.USART_Parity = USART_Parity_No;  //串口校验位   无校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1;   //串口停止位   1位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;   //串口字长   8位
	USART_Init(USART1, &USART_InitStruct);
	
	//第四部:发送开启USART,接收需要配置中断和NVIC
	USART_Cmd(USART1, ENABLE);
}


// 串口发送一个字节的数据
void Serail_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);                                 //发送传进来的一个字节的数据
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  //等待传送寄存器为空。确认发送完成再结束。
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

下面是Serial.h的文件:

#ifndef __SERIAL_H
#define __SERIAL_H

void Serial_Init(void);
// 串口发送一个字节的数据
void Serail_SendByte(uint8_t Byte);

#endif

最后就是主函数main.c的文件了:

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Serial.h"
#include "Delay.h"

uint8_t a=0x01;

int main(void)
{
	OLED_Init();       //oled  屏幕初始化
	Serial_Init();   //串口初始化
	
	
	
	while(1)
	{
		a++;
		Serail_SendByte(a);  //串口发送一个字节的0x42
		OLED_ShowHexNum(1,3,a,2);
		Delay_s(1);
	}
}

这样编译后下载到单片机中就能每隔1秒钟发送一个从1到255的十六进制数字了,发送完并在OLED上显示当前的数字,来看看最后电脑收到的串口数据吧:

由于是文本模式接收的数据所以有些是看不懂的字符,估计是阿斯克码中没有这个字符吧,下面是用十六进制的格式接收数据,来看看效果吧:

下面是利用DMA功能,自动的把要发送的数据搬运到串口发送寄存器DR中,完成自动发送,不占用CPU的资源,串口发送数据的同时CPU还可以处理其他的业务。

先介绍一下外设地址的由来:

串口1的基地址是0x40013800

串口的发送寄存器的偏移地址是:0x04

最后可以算得发送数据寄存器的地址为:0x40013800+0x04=0x40013804

知道这个地址主要是配置DMA的初始化结构体里面要用,下面就把代码展示出来吧!

MyDMA.c:

#include "MyDMA.h"

uint8_t yuan[10]={91,92,93,94,95,96,97,98,99,100};  //定义数据源数组,也就是要发送的数据



void MyDMA_Init(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);   //开启DMA时钟
	
	DMA_InitTypeDef DMA_InitStruct;              //定义DMA初始化的结构体变量
	DMA_InitStruct.DMA_BufferSize = 10;             //要发送的数据量是10个
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;     //DMA方向选择:外设是目的方
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;              //内存到内存的方式关闭
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)yuan;            //(内存)数据源的地址:强制32位的数组地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;    // 数据宽度为8位,字节型
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;            // 内存数据地址自增
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                       //DMA的模式为1次传输
	DMA_InitStruct.DMA_PeripheralBaseAddr = 0x40013804;                    //传输的目的方地址是DMA1的基地址+0x04的偏移地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;   // 外设的数据宽度为8位,字节型
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;          //外设的地址就是串口的DR寄存器,不用则增,保持不变
	DMA_InitStruct.DMA_Priority = DMA_Priority_Low;                     //DMA的优先级:低级
	DMA_Init(DMA1_Channel4, &DMA_InitStruct);                      //DMA初始化
	
	DMA_Cmd(DMA1_Channel4, ENABLE);                      //开启DMA1的通道4功能。
}



MyDMA.h:

#ifndef __MYDMA_H
#define __MYDMA_H

#include "stm32f10x.h"                  // Device header


void MyDMA_Init(void);



#endif

MyUsart.c:

#include "MyUsart.h"

void MyUsart_Init(void)
{
	RCC_APBxPeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);
	RCC_APBxPeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE);
	RCC_APByPeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_TX;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOx, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_RX;
	GPIO_Init(GPIOx, &GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = USART_Baud;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USARTx, &USART_InitStruct);
//	
//	USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);
//	
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//	NVIC_InitTypeDef NVIC_InitStruct;
//	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
//	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
//	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
//	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
//	NVIC_Init(&NVIC_InitStruct);
	
	USART_Cmd(USARTx, ENABLE);
}

void MyUsart_SendByte(USART_TypeDef* USARTy, uint8_t data)
{
	USART_SendData(USARTy, data);
	while( USART_GetFlagStatus(USARTy, USART_FLAG_TXE) ==RESET );
}

void MyUsart_SendArray(uint8_t *Arr, uint32_t num)
{
	uint32_t i=0;
	for(i=0; i<num; i++)
	{
		MyUsart_SendByte(USARTx, Arr[i]);
	}
}

void MyUsart_SendString(uint8_t *Str)
{
	while(*Str !=0)
	{
		MyUsart_SendByte(USARTx, *Str);
		Str++;
	}
}

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

void USART1_IRQHandler(void)
{
	uint8_t temp;
	if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
	{
		temp = USART_ReceiveData(USARTx);
		MyUsart_SendByte(USARTx, temp);
	}
}


int fgetc(FILE *f)
{
	int ch;
	while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);  //接收标志位为0的时候等待,为1的时候结束循环
	ch = USART_ReceiveData(USARTx);
	return ch;
}



MyUsart.c:

#include "MyUsart.h"

void MyUsart_Init(void)   //串口初始化函数
{
	RCC_APBxPeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);   //开启GPIOA的时钟
	RCC_APBxPeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE);  //开启串口1的时钟
	RCC_APByPeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);   //开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStruct;                     //定义GPIO初始化的结构体变量
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;           //推挽输出模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_TX;                //发送端口
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;        //频率
	GPIO_Init(GPIOx, &GPIO_InitStruct);                 //GPIOA初始化
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入模式
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_RX;               //接收端口
	GPIO_Init(GPIOx, &GPIO_InitStruct);                  //GPIOA初始化
	 
	USART_InitTypeDef USART_InitStruct;                  //定义串口初始化的结构体变量
	USART_InitStruct.USART_BaudRate = USART_Baud;                           //波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控为空
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;              //串口接收和发送模式
	USART_InitStruct.USART_Parity = USART_Parity_No;                         //校验:无
	USART_InitStruct.USART_StopBits = USART_StopBits_1;                        //停止位:1
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;                  //数据长度:8位
	USART_Init(USARTx, &USART_InitStruct);                                 //串口初始化
//	
//	USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);         //开启串口接收中断
//	
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);        //NVIC分组配置
//	NVIC_InitTypeDef NVIC_InitStruct;                     //定义NVIC初始化结构体变量
//	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;            //NVIC通道为串口1的中断通道
//	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;              //开启NVIC
//	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;    //抢占优先级:1
//	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;           //子优先级:1
//	NVIC_Init(&NVIC_InitStruct);                             //NVIC初始化
	
	USART_Cmd(USARTx, ENABLE);                        // 启用串口
}

void MyUsart_SendByte(USART_TypeDef* USARTy, uint8_t data)      //串口发送一个字节
{
	USART_SendData(USARTy, data);
	while( USART_GetFlagStatus(USARTy, USART_FLAG_TXE) ==RESET );
}

void MyUsart_SendArray(uint8_t *Arr, uint32_t num)     //串口发送一个数组
{
	uint32_t i=0;
	for(i=0; i<num; i++)
	{
		MyUsart_SendByte(USARTx, Arr[i]);
	}
}

void MyUsart_SendString(uint8_t *Str)       //串口发送一个字符串
{
	while(*Str !=0)
	{
		MyUsart_SendByte(USARTx, *Str);
		Str++;
	}
}

int fputc(int ch, FILE *f)             //重写printf函数
{
	MyUsart_SendByte(USARTx, ch);
	return ch;
}

void USART1_IRQHandler(void)                //串口接收中断函数
{
	uint8_t temp;
	if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
	{
		temp = USART_ReceiveData(USARTx);
		MyUsart_SendByte(USARTx, temp);
	}
}


int fgetc(FILE *f)                  //重写GetChar()函数
{
	int ch;
	while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);  //接收标志位为0的时候等待,为1的时候结束循环
	ch = USART_ReceiveData(USARTx);
	return ch;
}



MyUsart.h:

#ifndef __MYUSART_H
#define __MYUSART_H

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
/*******************************************************************/
/*     想要改变串口时只需要更改对应的串口后面的数字为1就好了,方便移植,其他的地方一概不用更改     */
#define Debug_USART1           1 
#define Debug_USART2           0

/*******************************************************************/

#if Debug_USART1          // 如果使用的是串口1  就编译

#define RCC_APBxPeriphClockCmd      RCC_APB2PeriphClockCmd
#define RCC_APByPeriphClockCmd      RCC_APB2PeriphClockCmd
#define RCC_APB2Periph_GPIOx        RCC_APB2Periph_GPIOA
#define RCC_APB2Periph_USARTx       RCC_APB2Periph_USART1

#define GPIOx                       GPIOA
#define GPIO_Pin_TX                 GPIO_Pin_9
#define GPIO_Pin_RX                 GPIO_Pin_10

#define USARTx                      USART1
#define USART_Baud                  9600

#elif Debug_USART2     // 如果使用的是串口2   就编译

#define RCC_APBxPeriphClockCmd      RCC_APB1PeriphClockCmd
#define RCC_APByPeriphClockCmd      RCC_APB2PeriphClockCmd
#define RCC_APB2Periph_GPIOx        RCC_APB2Periph_GPIOA
#define RCC_APB2Periph_USARTx       RCC_APB1Periph_USART2

#define GPIOx                       GPIOA
#define GPIO_Pin_TX                 GPIO_Pin_2
#define GPIO_Pin_RX                 GPIO_Pin_3

#define USARTx                      USART2
#define USART_Baud                  9600

#endif

void MyUsart_Init(void);

void MyUsart_SendByte(USART_TypeDef* USARTy, uint8_t Data);

void MyUsart_SendArray(uint8_t *Arr, uint32_t num);

void MyUsart_SendString(uint8_t *Str);

void USART1_IRQHandler(void);

#endif

main.c:

#include "stm32f10x.h"                  // Device header
#include "MyUsart.h"
#include "MYDMA.h"

int main(void)
{
	
	MyUsart_Init();      // 串口初始化
	MyDMA_Init();         //DMA初始化
	
	
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);   // 串口开启DMA发送的功能
	
	
	
	
	
	
}

这样编译程序后单片机就会通过串口自动的发送MyDMA.c中的数组中的内容了,和电脑通过数据线连接就能收到单片机发送来的数据了:

如果想要不停地发送的话,可以开启DMA的循环搬运模式,只要将DMA模式转换成循环搬运模式

DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; 

这样就可以不停的接收到单片机发来的数据了,就是每次发送的都是相同的内容,想要不同就要更改数组的内容了,还可以启用单次搬运模式,

利用USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);   // 串口开启DMA发送的功能

来触发一次发送的功能。这样做的好处就是不占用CPU的资源,发送数据的同时还可以处理其他的业务。

今天配置了一下串口利用DMA的自动搬运接收到的数据,自动存储到指定的数组内,这样我们在写程序时,就不用时刻的注意串口有没有接收到数据了,有数据了自动就放到数组里面了,不用我们操心了,也不占用CPU资源,想知道收到了什么数据时,直接查看数组的内容就好了,这样就省去了很多的麻烦事。先来看看最后的结果吧:

由此就可以证明我们每次发送的数据都被DMA自动搬运到了数组里面了,因为我们单片机发送的数组只有一个a,里面的内容也是固定的0,但是我们通过串口给单片机发送了数据后,单片机发送的a确变成了我们刚刚发送的数据,证明DMA一直在正常的工作。好了接下来就把代码发出来大家参考一下吧:

MyDMA.c:

#include "MyDMA.h"

uint8_t a[10];

void MyDMA_Init(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                   // 开启DMA1的时钟
	
	DMA_InitTypeDef DMA_InitStruct;                                      //定义DMA初始化的结构体变量
	DMA_InitStruct.DMA_BufferSize = 10;                                  //要搬运的数据数量是10个
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                      //方向:外设作为数据源
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                            //内存到内存的模式关闭
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)a;                     //内存的存储数据的地址是:32位的数组a
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         //内存中存储数据的宽度是:字节8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                 //内存中的地址需要自增(数组有10个元素)
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;                         //开启循环搬运模式
	DMA_InitStruct.DMA_PeripheralBaseAddr = 0x40013804;                  //外设数据源的地址:串口1的DR寄存器地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据的宽度是:字节8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;        //外设的数据地址不需要自增,只有这一个地址
	DMA_InitStruct.DMA_Priority = DMA_Priority_Low;                      //优先级:低级
	DMA_Init(DMA1_Channel5, &DMA_InitStruct);                            //DMA初始化
	
	DMA_Cmd(DMA1_Channel5, ENABLE);                                       //DMA开启通道五的搬运模式
}







MyDMA.h:

#ifndef __MYDMA_H
#define __MYDMA_H

#include "stm32f10x.h"                  // Device header

extern uint8_t a[10];

void MyDMA_Init(void);


#endif

主函数main.c:

#include "stm32f10x.h"                  // Device header
#include "MyUsart.h"
#include "MyDMA.h"
#include "led.h"

void delay(uint32_t time)
{
	while(time--);
}

int main(void)
{
	LED_Init();
	
	MyUsart_Init();

	MyDMA_Init();
	
	USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
	
	while(1)
	{
		LED(ON);
		delay(0xffffff);
		LED(OFF);
		delay(0xffffff);
		MyUsart_SendArray(a, 10);
	}
	
	
}

好了,就展示这么多吧,没有的模块可以删除或是自己写,编译后下载到单片机就能看到效果了。

作者:尚久龙

物联沃分享整理
物联沃-IOTWORD物联网 » STM32利用串口外设发送数据

发表回复