【stm32】嵌入式实验五 DMA 实验|数码管、串口、中断

配套软件系统:

MDK5、CubeMX、sscom5.13.1(串行口调试 程序)和 USBtoUARTDriver(USB 驱动程序)。

下面只提实验里涉及到的主要知识

DMA 数据传输方式:

普通模式

传输结束后(即要传输数据的数量达到零),将不再产生 DMA 操作。若开 始新的 DMA 传输,需在关闭 DMA 通道情况下,重新启动 DMA 传输。

循环模式

可用于处理环形缓冲区和连续数据流(例如 ADC 扫描模式)。当激活循环 模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响 应 DMA 请求

 

DMA相关接口函数

注意:只列出要用的函数。

//串口 DMA 方式接收函数

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

//串口 DMA 方式发送函数

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

//中断方式 接收数据 回调 函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

实验内容

说明

使用CubeMX对时钟,串口USART1,DMA,中断进行配置,这个步骤可以看这个视频:

【【13.1】减少CPU传输负载 DMA直接存储器访问—Kevin带你读《STM32Cube高效开发教程基础篇》】 https://www.bilibili.com/video/BV1Zm4y1q76N/?share_source=copy_web&vd_source=9332b8fc5ea8d349a54c3989f6189fd3

注意,利用之前的项目文件,另存为新的文件,免去了配置时钟,相关GPIO引脚的麻烦。若要使用hal库的延时函数,要把串口和DMA的中断优先级设置得比systick低。

模式的修改除了用CubeMX重新生成,也可以直接在uart.C里直接修改:

4c4867a3cb7f448caaabb82e8ebc5f03.png

实验一

1、采用 DMA 方式编写串口程序,串行口波特率设置为 115200bps,数据字长 8 位,停止 位 1 位,无校验。

(1)上位机向串行口发送一定长度的字符数据,开发板接收到数据后,将数据从串口 发回。测试单次发送与循环发送,比较并记录实验效果。

 

这里的单次发送和循环发送指的就是第二个标题里提到的普通模式和循环模式。代码如下:

数据缓冲区(全局变量)

/* USER CODE BEGIN PV */
uint8_t rxBuffer[10],proBuffer[10];
uint8_t RX_LEN=8;
/* USER CODE END PV */

回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance==USART1){
	  for(uint16_t i=0;i<RX_LEN;i++){
		    proBuffer[i]=rxBuffer[i];
		}
		
		HAL_UART_Transmit_DMA(huart,rxBuffer,RX_LEN+1);
	
	}
		
}
/* USER CODE END 0 */

主函数:为了比较HAL_UART_Receive_DMA在两种模式下的不同,这句要写在while外面,只调用一次。普通模式,接收一次之后不会再接收,需要重新调用这个函数启动DMA才能再次接收;而循环模式可以继续接收。在循环模式下,每发8个字符,开发板都会发回(在DMA接收数据的回调函数里调用了DMA发送函数,每次接收后都会发回);而普通模式只会发回一次。

/* USER CODE BEGIN 2 */
  __HAL_UART_DISABLE_IT(&huart1,UART_IT_TC);
	__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
	
	HAL_UART_Receive_DMA(&huart1,rxBuffer,RX_LEN);
	

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

 

(2) 上位机向串行口发送一定长度的字符数据,包括字母、数字、其他字符,开发板 接收到数据后,数字字符滚动显示在数码管上,其他字符从串口发回。

缓冲区(全局变量),每次接收一个字节的数据到rxdata里,flag==1时表示

/* USER CODE BEGIN PV */
uint8_t proBuffer[10],rxdata=0;
uint16_t RX_LEN=8;
int flag=0;//是否接收到了新的串口数据
/* USER CODE END PV */

数码管显示部分基本上是从前面的实验搬过来,table段码表,s存放要显示的数字,writebyte函数把要显示的一位数字送到数码管:

uint8_t table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x88,0x83,0xc6,0xa1,0x86,0x8e };
uint8_t s[]={0,0,0,0,0,0,0,0};
void write_byte(uint8_t date){
    unsigned char i;
	for(i=0;i<8;i++){
	  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,(date>>(7-i))&0x01);
		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,1);
		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,0);
	}
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,1);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,0);
 
}

 回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance==USART1){
		
	  if(rxdata>='0'&&rxdata<='9')
		{
		/*
			//从左往右滚动
			for(int i=7;i>0;i--){
		    proBuffer[i]=proBuffer[i-1];
		}
		proBuffer[0]=rxdata;*/
			//从右往左
		for(int i=0;i<7;i++){
		    proBuffer[i]=proBuffer[i+1];
		}
		proBuffer[7]=rxdata;
			
		}
		else HAL_UART_Transmit_DMA(huart,&rxdata,1);
		
		flag=1;
	}
		
}

 主函数,接收设置为循环模式,cnt是用来计时的,经过一定时间后才滚动一次,不然太快了,flag为1时说明收到了新的数据,数码管显存更新,因为串口发来的是字符,实现的数码管显示函数是按数字对应的,例如发送了'1',要把'1'转化成1,实现滚动效果,同样地对s缓存区进行一个循环左移:

	HAL_UART_Receive_DMA(&huart1,&rxdata,1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	int cnt=0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
		if(flag==1){
		for(uint16_t i=0;i<8;i++){
			 if(proBuffer[i]>9)
		    s[i]=proBuffer[i]-'0';
		}
		flag=0;
		}
		
		cnt++;
		if(cnt==100){
		uint8_t temp=s[0];
		for(int i=0;i<7;i++){
		    s[i]=s[i+1];
		}
		s[7]=temp;
		cnt=0;
		}
	
		
		for(int i=8;i<16;i++){
		write_byte(table[s[15-i]]);
		HAL_GPIO_WritePin(GPIOF,0x01<<i,0);
		HAL_Delay(1);
		HAL_GPIO_WritePin(GPIOF,0x01<<i,1);
		}
  }

2、采用 DMA 方式编写串口程序,串行口波特率设置为 115200bps,数据字长 8 位,停止 位 1 位,无校验。实现简易“心跳包”程序。

(1)设计计数器 1,计数器 2,分别采集和记录按键 KB1、KB2 按下的次数,并将实时 计数结果显示在数码管上。

KB1和KB2的中断配置使用CubeMX,之前配置过,流程不再赘述。注意中断优先级的设置,这里我设的2。

缓存区

key[0]对应KB1

/* USER CODE BEGIN PV */
uint8_t Keycnt[2]={0,0};
uint8_t cntTo4[10]={'0','0','0','0','0','0','0','0'};
uint8_t rxbuff[5];
/* USER CODE END PV */

 回调函数,显示函数,协议处理(这里的协议用的数据帧五个字节,仿照上个实验):

uint8_t table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x88,0x83,0xc6,0xa1,0x86,0x8e };
uint8_t s[]={0,0,0,0,0,0,0,0};
void write_byte(uint8_t date){
  unsigned char i;
	for(i=0;i<8;i++){
	  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,(date>>(7-i))&0x01);
		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,1);
		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,0);
	}
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,1);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,0);
 
}

void todis(){
	s[7]=Keycnt[1]%10;
	s[6]=Keycnt[1]/10%10;
	s[5]=Keycnt[1]/100%10;
	s[4]=Keycnt[1]/1000%10;
	
	s[3]=Keycnt[0]%10;
	s[2]=Keycnt[0]/10%10;
	s[1]=Keycnt[0]/100%10;
	s[0]=Keycnt[0]/1000%10;
	
	for(uint8_t x=0;x<8;x++)
	{
	   cntTo4[x]=s[x]+'0';
	}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	
  if(GPIO_Pin==GPIO_PIN_0) Keycnt[0]++;
	if(GPIO_Pin==GPIO_PIN_1) Keycnt[1]++;
	
	

	todis();
	HAL_UART_Transmit_DMA(&huart1,cntTo4,8);
	HAL_Delay(30);
	
	
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){

	if(huart->Instance==USART1){
		
		//HAL_UART_Transmit_DMA(&huart1,rxbuff,5);
		if(rxbuff[0] == 0xAA) {
            if(rxbuff[1] == 0x01) {
                if(rxbuff[2] == 0x00) {
                    HAL_UART_Transmit_DMA(&huart1,cntTo4,8);
                }
               // else if(rxbuff[2] == 0x01) { }
            }
            else if(rxbuff[1] == 0x02) {
                if(rxbuff[2] == 0x02) {
								Keycnt[0]++;
									todis();
								}
                else if(rxbuff[2] == 0x03) {
								Keycnt[0]--;
									todis();
								
								}
								else if(rxbuff[2] == 0x04){
								Keycnt[1]++;
								todis();
								}
								else if(rxbuff[2] == 0x04){
								Keycnt[1]--;
								todis();
								}
            }
        }
		
		
	}
	
}

主函数:

/* USER CODE BEGIN 2 */

  HAL_UART_Receive_DMA(&huart1,rxbuff,5);
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
		for(int i=8;i<16;i++){
		write_byte(table[s[15-i]]);
		HAL_GPIO_WritePin(GPIOF,0x01<<i,0);
		HAL_Delay(1);
		HAL_GPIO_WritePin(GPIOF,0x01<<i,1);
		}
		
		
  }

 数码管前四位显示KB1按键计数值,后四位KB2。按下一次,数码管更新显示。并将结果发送给串口。上位机发送相应指令,可以查询结果,加减计数值。

注意:这里涉及到按键,按键有抖动,延时已经消抖,但是要改一下中断函数IRQHandler里两个函数的顺序,具体请回看按键中断这个实验的文章,在专栏里翻一下就能找到。

先做完处理再清除标志位,这样按键抖动就不会影响了。所以,涉及到按键的外部中断需要特别处理这里的顺序,另外用CubeMX重新生成代码后,这里会恢复成原来的样子,要记得改。

(2)设计通信协议,将计数器 1、计数器 2 的计数结果封装成数据帧,以 DMA 方式向串 行口发送,要求发送的数据与数码管上显示的数据同步。

已经包括在上面的代码里。

项目文件请看个人主页的资源。

https://download.csdn.net/download/qq_61814350/89317514?spm=1001.2014.3001.5501

 

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guts350

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值