STM32单片机基于DMA的串口不定长度数据传输的库函数开发

摘要

DMA叫做直接内存访问,是不通过CPU进行对内存的操作的技术,可以增加效率

USART1是用于通信的串口,我们要做的就是将USART1传入的不定大小的数据用DMA传输到我们指定的数组中

芯片STM32F103C8T6 使用了USART1的USART_IT_IDLE中断

做法

先用DMA获取USART输入的数据,这需要我们准备一个足够大的接收缓冲数组例如我定义了一个256字节的数组

接下来,我们通过DMA的DMA_GetCurrDataCounter()函数获取剩余的计数量,来计算我们传入了多少个字节的数据:

传入数据量 = 256 - 剩余计数量

这个传入数据量也就是我们DMA接收到的数据大小,单位是字节,一个字节是8个bit位

然后我们可以向串口输出我们接收到的数据。

程序主要代码

#include <stm32f10x.h>
#include <string.h>
#include <stdlib.h>

#define Data_RX_SIZE 256

char buf[Data_RX_SIZE]; /*定义DMA接收缓存区*/

/*************************USART1******************************/
void uart_Init(uint32_t BaudRate)
{
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /*设置中断优先级组*/

  USART_InitTypeDef USART_InitStruct;
  NVIC_InitTypeDef NVIC_InitStruct;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); /* 开启串口1时钟 */

  USART_InitStruct.USART_BaudRate = BaudRate;/* 比特率 */
  USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStruct.USART_Mode  = USART_Mode_Tx | USART_Mode_Rx;/* 全双工模式 */
  USART_InitStruct.USART_Parity = USART_Parity_No;
  USART_InitStruct.USART_StopBits = USART_StopBits_1;
  USART_InitStruct.USART_WordLength = USART_WordLength_8b;
  USART_Init(USART1, &USART_InitStruct);/* 串口配置初始化 */


  GPIO_init_With_Mode(GPIOA,GPIO_Pin_9,GPIO_Mode_AF_PP); /* PA9 复用推挽输出*/
  GPIO_init_With_Mode(GPIOA,GPIO_Pin_10,GPIO_Mode_IN_FLOATING); /*PA10 浮空输入*/

  NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
  NVIC_Init(&NVIC_InitStruct);/* 中断配置初始化 */

  USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
/********************************************
    *开启空闲检测中断,例如你向串口传入"Hello"
    *当Hello传递完了之后才会触发这个中断
    *而不是传个H触发一次,传个e再触发一次
********************************************/
    USART_Cmd(USART1,ENABLE);    /*使能USART*/
    DMA1_init((uint32_t)(&(USART1->DR)),(uint32_t)buf,Data_RX_SIZE); /*自定义DMA1函数,初始化DMA1*/

}


/**************************DMA1********************************/
void DMA1_init(uint32_t AddrSend,uint32_t AddrReceive,uint16_t size)
{

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); /*使能DMA1时钟*/

  NVIC_InitTypeDef NVIC_InitStruct;
  DMA_InitTypeDef DMA_InitStruct;
  NVIC_Init(&NVIC_InitStruct);

    /* DMA外设方面设置 */
    DMA_InitStruct.DMA_PeripheralBaseAddr = AddrSend;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    /* DMA内存方面设置 */
    DMA_InitStruct.DMA_MemoryBaseAddr = AddrReceive;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;

    DMA_InitStruct.DMA_BufferSize = size;        /*DMA缓冲区大小!*/
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; /*DMA传输方向,以外设为Srouce(源头)*/
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;    /*禁止软件触发,因为我们用USART的接收触发方式,属于硬件触发方式,也就是非M2M方式*/

    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;    /*普通模式,需要手动去重设计数器*/
    DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;    /*优先级,随便设置,反正也就一个DMA*/

  DMA_Init(DMA1_Channel5, &DMA_InitStruct);

  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);    /*USART的DMA使能,使用读取信息给DMA*/
  DMA_Cmd(DMA1_Channel5,ENABLE);    /*DMA1的通道5使能,在stm32f103中DMA1的通道五对应USART1的数据读取寄存器DR*/
}

/*********************USAER1中断***********************/
void USART1_IRQHandler()
{
    if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET) //检查中断是否发生
    {    

        volatile u8 temp;
        temp=USART1->SR;
        temp=USART1->DR;    /*清除IDLE中断需要读取两个寄存器的信息*/

        DMA_Cmd(DMA1_Channel5,DISABLE); /*关闭DMA传输,进行数据处理,数据处理完再开启*/

        uint16_t length = Data_RX_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); /*当前帧 数据长度*/

        DMA_Show(length);   /*展示DMA接收到的数据*/

        DMA_SetCurrDataCounter(DMA1_Channel5,Data_RX_SIZE); /*重新给计数器赋值*/

        DMA_Cmd(DMA1_Channel5,ENABLE);   /*重新打开计数器*/

        USART_ClearFlag(USART1,USART_FLAG_IDLE);    /*清除串口空闲中断标志位*/
    }

}

/************************DMA_Show()************************/
void DMA_Show(uint16_t len)
{
    uart_cout("此时buf数组中接收到的值为:");

    char * Temp_buf = (char*)malloc(len); /*开辟堆区空间,用于选择合适的输出范围*/

    strncpy(Temp_buf,buf,len);    /*将大小固定的数据写入*/

    uart_cout(Temp_buf); /* 输出数据 */

    uint16_t Remaining_Counts = DMA_GetCurrDataCounter(DMA1_Channel5);    /*获取当前DMA缓存空间剩余*/
    uart_cout_add_variable("DMA剩余空间大小(字节):",Remaining_Counts);

    uint16_t Counts =  Data_RX_SIZE - Remaining_Counts;    /*获取数据大小*/
    uart_cout_add_variable("接收到数据大小(字节):", Counts);

    memset(Temp_buf,0,len); /*清除Temp_buf,防止前后传入的两次数据相互叠加*/
    free(Temp_buf);   /*释放堆区*/
    Temp_buf = NULL; /* 将指针设置成NULL,避免悬挂指针*/
}

/************************main************************/
int main(){
    uart_Init(115200); /*因为使用的中断,所以主函数里面只需要初始化就可以了*/
    while(1);
}

程序次要代码

GPIO_init_With_Mode

void GPIO_init_With_Mode(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin , GPIOMode_TypeDef GPIO_Mode)
{
  GPIO_InitTypeDef GPIO_InitStruct;
    /*判断GPIO,不同的GPIO给不同的时钟*/
  if(GPIOx == GPIOA)
    {
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    }
  else if(GPIOx == GPIOB)
    {
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    }
  else if(GPIOx == GPIOC)
    {
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    }

  GPIO_InitStruct.GPIO_Mode = GPIO_Mode;
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOx, &GPIO_InitStruct);
}

uart_cout

//传入数组指针,通过串口1输出数组
void uart_cout(const char* str)
{

  unsigned int len = strlen(str);
  delay_us(100);
  for(int i = 0; i < len ; i++)
    {
      USART_SendData(USART1,str[i]);
      delay_us(100); // 延迟用于等待串口输出信息
    }
  while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);

}

delay_us

void delay_us(uint16_t xus)
{
  RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // 使能 TIM1 时钟

  TIM1->PSC = SystemCoreClock/ 1000000 - 1; // 设置预分频器的值,实现 1 秒内的计数次数
  TIM1->ARR = xus * 2000 - 1; // 设置自动重载寄存器的值,实现指定的延迟时间
  TIM1->CNT = 0; // 将计数器的值清零
  TIM1->CR1 |= TIM_CR1_CEN; // 启动计数器

  while ((TIM1->SR & TIM_SR_UIF) == 0); // 等待计数器溢出中断

  TIM1->SR &= ~TIM_SR_UIF; // 清除计数器溢出标志位
  TIM1->CR1 &= ~TIM_CR1_CEN; // 停止计数器

  RCC->APB2ENR &= ~RCC_APB2ENR_TIM1EN; // 关闭 TIM1 的时钟
}

测试效果

参考:
田野的小站

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: stm32f4标准库函数是由ST公司提供的一套软件函数库,针对stm32f4系列单片机开发而设计。该函数库包含了各种控制器的初始化函数、IO口控制函数、定时器控制函数、中断处理函数等。 其中,控制器的初始化函数用于对各个控制器进行初始化,以实现控制器的各种功能。例如,GPIO_Init函数用于初始化IO口,使用户可以控制IO口的状态。而定时器控制函数则可用于实现各种定时器功能,如定时中断、计时功能等。 此外,stm32f4标准库函数还提供了各种中断处理函数,用于处理不同的中断事件。例如,GPIO中断处理函数可用于处理IO口的中断事件,而定时器中断处理函数则可用于处理定时器的中断事件。 总体而言,stm32f4标准库函数stm32f4系列单片机开发中不可或缺的一部分,能够大大简化开发人员的开发工作,提高开发效率。 ### 回答2: STM32F4系列是意法半导体推出的Cortex-M4内核的微控制器系列。为方便开发人员利用该系列微控制器进行项目开发,意法半导体提供了一份标准库,其中包含了大量的函数库来支持STM32F4系列微控制器的各种外设。本文将对STM32F4标准库函数进行说明。 STM32F4标准库函数可以在意法半导体的官方网站上下载。库文件主要包括以下几个部分: 1. CMSIS: Cortex Microcontroller Software Interface Standard (ARM Cortex微控制器软件接口标准)。包括系统时钟配置、中断、NVIC(Nested Vectored Interrupt Controller)、Systick(全局定时器)等相关的函数库。 2. Device驱动:该部分是针对当前芯片的设备驱动程序,包括GPIO、定时器、SPI、I2C、串口等模块。 3. Peripherals驱动:该部分是针对当前芯片的外设驱动,包括ADC(模数转换器)、DMA(直接存储器访问)、DAC(数模转换器)、SDIO(Secure Digital Input/Output)、USB(通用串行总线)等模块。 4. Middleware:该部分是意法半导体提供的一些中间件,可供用户自行选择。主要包括LWIP(Lightweight IP协议栈)、USB Host/Device库等。 需要注意的是,在使用STM32F4标准库进行开发时,需要对库进行配置,以满足对外设或功能的不同要求。 总的来说,STM32F4标准库函数提供了大量的函数库,可以有效地进行硬件编程,为用户带来方便、高效的微控制器开发体验。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值