STM32学习笔记(串口+DMA)

STM32学习笔记(串口+DMA)

  • 2020.4.20
    • 数据位大小与大端小端:
      • 实际应用:
    • `assert_param();`函数的理解:
    • 串口配合DMA的使用方法回顾:
      • 实现平台说明:
      • 重要的初始化内容:
      • 需要额外注意的要点:
      • 串口DMA总结:
  • 2020.4.21
      • 串口接收的代码实现:
  • 2020.4.22
      • 数据拆分宏定义:
      • 串口发送的函数实现:
      • 串口发送的代码实现 :
      • 串口DMA的后记 ,总结:

2020.4.20

  • 数据位大小与大端小端:

  1. 大端模式(big endian):低地址存放高有效字节

  2. 小端模式(little endian):低字节存放低有效字节

    设一个int形变量0x12345678 两个数就是一字节

    ​ 高有效字节——>低有效字节: 12 34 56 78

    低地址位 高低址位

    大端12 34 56 78

    小端: 78 56 34 12

    STM32单片机的存储方式为小端模式

  int main(void ){
     unsigned int x =0x12345678;
     unsigned char *p = (unsigned char *)&x; //取首地址后转换为字符数组,各地址上存储的数据
     printf("%0x %0x %0x %0x",p[0],p[1],p[2],p[3]); //且char为8bit 故两位两位区分
     return 0;
  }
  //依次输出78 56 34 12,可以看出电脑存储数据也是小端排序
 
  
  1. 在C程序中常见uint8_t(实为unsigned char,与char大小同为一个字节,仅多一个符号),uint32_t(实为unsigned int), uint16_t(实为unsigned short int)

typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;

​ 其中,unsigned char无符号位,可以表示0~ 255。char能表示的数据范围是-128~127。

 

实际应用:

​ 衍生概念:

MSB: Most Significant Bit ------- 最高有效位
LSB: Least Significant Bit ------- 最低有效位

在SPI及其他通信协议中,该概念有具体使用。
 

 

  • assert_param();函数的理解:

    #ifdef USE_FULL_ASSERT
    
    #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t*)__FILE__, __LINE__))
    //双目运算符判断expr的值的真假  (void)0什么都不执行
    
    void assert_failed(uint8_t* file, uint32_t line);
    #else
    #define assert_param(expr) ((void)0)
    #endif 
    

    真值判断函数: 作为一个函数运行过程中,对形参的有效性进行检查。

    assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));IS_DMA_ALL_PERIPH(DMAy_Channelx)为对DMAy_Channelx的认证过程,若有效则返回空函数。

    常见于函数体中,看了感觉没啥用。

     

  • 串口配合DMA的使用方法回顾:

实现平台说明:

本实验使用STM32F103c8t6配合CP2102串口电平转换模块进行。
基于标准库

 
 

  • 重要的初始化内容:

  1. DMA初始化时应预留的参数接口 , 在函数外部能修改通道、源地址、目标地址和传输数据量等几个参数,必要时做出声明。
  • DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;

//使用串口接收数据的寄存器的地址((uint32_t)&取地址并强制类型转换)作为DMA传输的外设基地址

  • DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;

//提前定义数组rx_buf[RX_BUFF_SIZE]作为DMA的数据存储地址(存储器)

//通常习惯uint8_t rx_buf[2048];然后强制类型转换为uint32_t,不懂。

 

 

在串口中断中操作DMA时,一般设置串口中断为空闲中断

DMA的接收中断设置为传输成功

USART_ITConfig( USARTx, USART_IT_IDLE , ENABLE );

DMA_ITConfig(DMA1_Stream6,DMA_IT_TC, ENABLE);

 

  1. DMA相关的功能函数

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)

//设置DMA通道中传输数据的数量

//在开启DMA时,应先关闭DMA,再设置传输数据缓存的大小,再开启DMA

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

//得到DMA通道中剩余数据单元的数量

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

//获取DMA的通道状态

 

串口接收中操作DMA时,一般设置串口中断为空闲中断,串口数据流停止后总线空闲产生空闲中断。(在伴随RXNE位被置高后,IDLE位才会被置起,相应产生中断)

USART_ITConfig( USARTx, USART_IT_IDLE , ENABLE );

​ 在串口中断中需要进行以下几件事

​ (应先判断中断)

 > if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
 >     {
 >         clear = USART1->SR; //或者使用USART_ClearITPendingBit();
 >         clear = USART1->DR;
 > 	}
  1. 关闭串口接收DMA通道,防止数据干扰,便于重新配置DMA.

  2. 置位接收完成标志位。

  3. 处理接收buffer里的数据。

  4. 重新设置DMA接收字节数。(大于等于可接收的最大字节数)

  5. 开启DMA,等待下次接收数据。

     

DMA的接收中断设置为传输错误中断

DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);

 

  • 串口发送中操作DMA时,对串口的操作被最大限度保留,只需要设置DMA为传输成功中断

DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);

在中断中的操作较为简单,只需要清除标志位关闭DMA即可。

难点为调用数据发送函数的编写,需要调用DMA_Cmd()函数开始传输。且需要对数据类型进行一定的处理。

 

  • 需要额外注意的要点:

  1. 在开启DMA时,应先关闭DMA,再设置传输数据缓存的大小,再开启DMA。 且串口接收中,DMA保持开启。

  2. 在串口发送时,DMA仅在数据传输时(发送函数中)打开,传输成功后关闭DMA。

//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
	DMA_Cmd(DMA_CHx, DISABLE );  //关闭该通道     
 	DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//设置缓存大小
 	DMA_Cmd(DMA_CHx, ENABLE);  //开启该通道DMA
}	  

 

{ 
 	DMA_Cmd(DMA_CHx, DISABLE );  //关闭该通道     
  	DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//设置缓存大小
  	DMA_Cmd(DMA_CHx, ENABLE);  //开启该通道DMA
 }	  

 

 

  • 串口DMA总结:

    接收:能在不打扰主程序的情况下,借道DMA将应该从串口传输的数据存入rxbuffer;侧面缓解了主机的运行压力,在进入IDLE总线空闲之前不需要对数据进行任何处理,同时计算数据位数,实现了不定长数据接收。直接用串口接收数据时,在RXNE位被置高后就需要接收数据
     
     

2020.4.21

串口接收的代码实现:

  • 今日配置了一套串口接收,实现了接收上位机发码的基本功能,可以在缓存区收到正确的数据

    附上乱套的代码, 供我以后参考使用。

 static void NVIC_Configuration(void)
 {
   NVIC_InitTypeDef NVIC_InitStructure;
   
 
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
   
 
   NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
 
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 
   NVIC_Init(&NVIC_InitStructure);
 }
 
 uint8_t rx_buf[20];
 uint8_t rx_buffer_test[20];
 static void USARTx_DMA_Rx_Config(void)
 {
 	DMA_InitTypeDef DMA_InitStructure;
 
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
 
 	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
 	
 	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;
 
 	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//DMA_DIR_PeripheralToMemory
 
 	DMA_InitStructure.DMA_BufferSize = USART_RX_BUFF_SIZE;
     
 	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 	
 	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
 	
 	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
 	
 	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	 

 
   DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
 
   DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; 
 
   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
 	   
 	DMA_Init(DMA1_Channel5, &DMA_InitStructure);		
 
 	DMA_ClearFlag(DMA1_FLAG_TC5);
 	DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);
 
 	DMA_Cmd (DMA1_Channel5,ENABLE);
 	 
 }
 
 
 
 void USART_Config(void)
 {
 	GPIO_InitTypeDef GPIO_InitStructure;
 	USART_InitTypeDef USART_InitStructure;
 
 
 	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
 	
 	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
 
 
 	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
 
 
 	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
 
 	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
 
 	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
 
 	USART_InitStructure.USART_StopBits = USART_StopBits_1;
 
 	USART_InitStructure.USART_Parity = USART_Parity_No ;
 
 	USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;
 
 	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 
 	USART_Init(DEBUG_USARTx, &USART_InitStructure);
 	
 	NVIC_Configuration();
 	
 	USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);  
 
 	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE); 
 
 	USARTx_DMA_Rx_Config();
 
 	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
 
 	USART_Cmd(DEBUG_USARTx, ENABLE);	    
 }
 
 

自己写的注释拷过来乱码了。

//中断部分
void USART1_IRQHandler(void)
{

	 uint8_t clear = clear;  
	b++;
	
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {
        clear = USART1->SR;
        clear = USART1->DR;
		  	RxCounter = USART_RX_BUFF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
				RxStatus = 1;   //自行设置了接收完成标志位,在主函数中检测终端的发生
				USART_ClearITPendingBit(USART1, USART_IT_IDLE); 
        DMA_Cmd(DMA1_Channel5, DISABLE);          
    }
}

//主函数中循环部分
while(1)
    {
        if(RxStatus == 1)
        {
            RxStatus = 0;
          

            while(RxCounter--)//对缓存数组逐个操作
            {
   				OLED_ShowString(0,48,rx_buf,16);
   				OLED_Refresh();
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
            }

            memset(rx_buf, 0, i); // 清空缓存区
            RxCounter = 0; //置低

//          DMA1_Channel5->CNDTR = BufferSize;
            DMA_SetCurrDataCounter(DMA1_Channel5, USART_RX_BUFF_SIZE);//保持数据长度不变
            DMA_Cmd(DMA1_Channel5, ENABLE);     
        }
    }

我当时因为想把接收到的数据传给OLED屏幕来显示,函数端口要求导入uint8_t变量(通常使用该变量储存字符型数据,详见2020.4.20),便定义了uint8_t rx_buf[20]作为DMA的缓存数组,且串口调试助手也使用了CHR发送格式

最终能将字符正确显示到OLED屏幕端口

时间过于晚,睡了睡了

 

 

2020.4.22

数据拆分宏定义:

我参考着匿名四轴上位机发码的例程,重新体会了一下数据传输过程中对不同类型数据的处理方法

#define BYTE0(dwTemp)       (*(char *)(&dwTemp))
#define BYTE1(dwTemp)       (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp)       (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp)       (*((char *)(&dwTemp) + 3))

其中,默认的dwTempfloat类型(4Byte),可以拆分为四个指向char的指针,并指出相应内容。

 

串口发送的函数实现:

我自己配置了串口发送的程序,预留DMA缓存区为uint8_ttx_buf[40]。假设我有一组浮点型数组data[8]需要由单片机发送至上位机,可以直接使用匿名四轴函数处理发送数据

依次将每一个data传入函数,函数将一个浮点型数据拆分重组为一个数据帧:包含四个帧头位、四个数据位和一个求和校验位。(个人理解,不规范命名),可以按要求读取单片机中的数据。

 
 

串口发送的代码实现 :

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
 
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);
	
	
	 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;  

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
	
}


uint8_t rx_buf[20];

uint8_t tx_buf[40];

static void USARTx_DMA_Tx_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
//	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);   

	DMA_DeInit(DMA1_Channel4);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buf;  
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
	DMA_InitStructure.DMA_BufferSize = USART_TX_BUFF_SIZE;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel4, &DMA_InitStructure);
	
	
	DMA_ClearFlag(DMA1_IT_GL4);  
	DMA_Cmd(DMA1_Channel4,DISABLE);
	DMA_ITConfig(DMA1_Channel4,DMA_IT_TC, ENABLE);
}

static void USARTx_DMA_Rx_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;


	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
	
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;

	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC
	
	DMA_InitStructure.DMA_BufferSize = USART_RX_BUFF_SIZE;
   
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	 

	//DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	

	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; 

	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
			   
	DMA_Init(DMA1_Channel5, &DMA_InitStructure);		

	DMA_ClearFlag(DMA1_FLAG_TC5);
	DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);

	DMA_Cmd (DMA1_Channel5,DISABLE);
	 
}


void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;


	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	

	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);


	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);


	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	

	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;

	USART_InitStructure.USART_WordLength = USART_WordLength_8b;

	USART_InitStructure.USART_StopBits = USART_StopBits_1;

	USART_InitStructure.USART_Parity = USART_Parity_No ;

	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(DEBUG_USARTx, &USART_InitStructure);
	
	NVIC_Configuration();
		
	USART_Cmd(DEBUG_USARTx, ENABLE);
	
	USARTx_DMA_Rx_Config();

	USARTx_DMA_Tx_Config();

	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);

	USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);  

	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE); 

	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); 
	    
}

//**********************************************//
int Flag_Uart_Busy;
int check_flag=0;
uint8_t uart1_send_buff[60];

void New_Send_Data(uint8_t *data,uint16_t size)
      {  extern int f;f++;
		  Flag_Uart_Busy = 1;
		  memcpy(tx_buf,data,size); 		  
				DMA1_Channel4->CNDTR=(uint16_t)size;
        DMA_Cmd(DMA1_Channel4,ENABLE);

	  }
void send_odm_msg(float * data)
{ 
	int i=0;
	uint8_t sum=0;
	uart1_send_buff[0] = 0xAA;
	uart1_send_buff[1] = 0xAA;
	uart1_send_buff[2] = 0xF1;
	uart1_send_buff[3] = 4;
	for(i=0;i<1;i++) 
	{
		uart1_send_buff[i*4+0+4] = BYTE3(*(data+i));
		uart1_send_buff[i*4+1+4] = BYTE2(*(data+i));
		uart1_send_buff[i*4+2+4] = BYTE1(*(data+i));
		uart1_send_buff[i*4+3+4] = BYTE0(*(data+i));
	}
	for(i=0; i<8; i++)
	{
		sum+=uart1_send_buff[i];
	}
	uart1_send_buff[8] = sum;	
	
	New_Send_Data(uart1_send_buff,9);
}

void send_check(uint16_t data)
{
    uint8_t uart6_send_buff[8]; 
	uint16_t sum=0,i;
	uart6_send_buff[0] = 0xAA;
	uart6_send_buff[1] = 0xAA;
	uart6_send_buff[2] = 0xEF;
	uart6_send_buff[3] = 2;
    uart6_send_buff[4] = 0x10;
    uart6_send_buff[5] = data;
  
  for(i=0;i<6;i++)
  {
    sum+=uart6_send_buff[i];
  }
  uart6_send_buff[6] = sum;
  New_Send_Data(uart6_send_buff,7);
}
void Send_CheckData(void)
{
  if(check_flag)
  {
    send_check(check_flag);
    check_flag=0;
  }
}

//DMA传输完成中断,在这里清除标志位,关闭DMA。
void DMA1_Channel4_IRQHandler(void)
{
	e++;
	if(DMA_GetITStatus(DMA1_FLAG_TC4))
	{
		DMA_ClearFlag(DMA1_FLAG_GL4);//DMA1_FLAG_GL4应该包含TC位
		DMA_Cmd(DMA1_Channel4,DISABLE);
		TxStatus=1;       //制造传输完成标识,给前台程序提示串口发送完成。
	}
}

 
 

串口DMA的后记 ,总结:

 

  • 配置过程中还犯了个低级错误,忘记给DMA中断分配中断优先级,导致进不去传输成功中断,无法正常工作。以后一定留心,有中断就有中断优先级分配表,减少智障几率。
     
  • 对串口的回顾到此结束,串口是我接触STM32后使用的第一个通信协议,对比SPI和IIC不设时钟线,也不像CAN一样使用差分信号,但串口可与USB接口通过电平转换相适配,应用广泛;在设备调试过程中可以使用串口与上位机进行通讯,传递相应的数据,十分有用。个人见解,仅供参考,若有错误,请多包含。

你可能感兴趣的:(STM32学习笔记(串口+DMA))