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);
}
}
好了,就展示这么多吧,没有的模块可以删除或是自己写,编译后下载到单片机就能看到效果了。
作者:尚久龙