stm32数据传输

简介

DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。它的作用就是解决大量数据转移过度消耗CPU资源的问题。
在stm32(大容量)中有两个DMA,DMA1有7个通道,DMA2有3个通道
串口1则是在DMA1通道4,各个通道如下图:
在这里插入图片描述
在这里插入图片描述

DMA程序配置

用标准库来配置有点麻烦,很多地方都要设置对才行。最重要的就是DMA结构体

typedef struct
{
 uint32_t DMA_PeripheralBaseAddr;
uint32_t DMA_MemoryBaseAddr; 
 uint32_t DMA_DIR; 
 uint32_t DMA_BufferSize; 
 uint32_t DMA_PeripheralInc; 
 uint32_t DMA_MemoryInc; 
 uint32_t DMA_PeripheralDataSize;
 uint32_t DMA_MemoryDataSize; 
 uint32_t DMA_Mode; 
 uint32_t DMA_Priority; 
 uint32_t DMA_M2M; 
}DMA_InitTypeDef;

第一个参数 DMA_PeripheralBaseAddr 用来设置 DMA 传输的外设基地址,比如要进行串口DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR 的地址,表示方法为&USART1->DR
第二个参数DMA_MemoryBaseAddr为内存基地址,也就是我们存放DMA传输数据的内存地址一般都是数组。
第三个参数 DMA_DIR 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,这里我们设置为从内存读取数据发送到串口,所以外设自然就是目的地了,所以选择值为 DMA_DIR_PeripheralDST。
第四个参数 DMA_BufferSize 设置一次传输数据量的大小,这个很容易理解。
第五个参数 DMA_PeripheralInc 设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加 1,这里因为我们是一直往固定外设地址&USART1->DR
发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable;
第六个参 数 DMA_MemoryInc 设置传输数据时候内存地址是否递增。这个参数 和DMA_PeripheralInc 意思接近,只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,毫无疑问内存地址是需要递增的,所以值为 DMA_MemoryInc_Enable。
第七个参数 DMA_PeripheralDataSize 用来设置外设的数据长度是为字节传输(8bits),半字传输 (16bits) 还 是 字 传 输 (32bits) , 这 里 我 们 是 8 位 字 节 传 输 , 所 以 值 设 置 为
DMA_PeripheralDataSize_Byte。
第八个参数 DMA_MemoryDataSize 是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输 DMA_MemoryDataSize_Byte。
第九个参数 DMA_Mode 用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采集 64 个字节发送到串口,如果设置为重复采集,那么它会在 64 个字节采集完成之后继续从内
存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,不会中断,大家在实验中可以修改这个参数测试一下。
第十个参数是设置== DMA 通道的优先级==,有低,中,高,超高三种模式,这个在前面讲解过,这里我们设置优先级别为中级,所以值为 DMA_Priority_Medium。如果要开启多个通道,那么
这个值就非常有意义。
第 十 一 个 参 数 DMA_M2M 设 置 是 否 是 存 储 器 到 存 储 器 模 式 传 输 , 这 里 我 们 选 择DMA_M2M_Disable。

例子:
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
	DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器

DMA有很多用法,同时很多外设也可以开启DMA传输,比如USART,ADC,一般都是外设和内存(数组)之间的传递,很少有外设到外设之间的传递,如果需要可以去网上找例程,或者通过内存做过度,外设1DMA到内存,然后内存到外设2DMA。

一般的程序都是main然后一个while配套一些中断函数裸机开发,实时性差,而采用实时操作系统的开发方式则单片机的实时性大大提高,当然这样的开发难度要高。一般while中开启DMA传输,然后中断函数跟新要传送的数据,同时再设置标标志位flag。

int main()
{
	初始化函数
	while1{
		if(Receive_Flag=1//即数据更新了,该标志位在中断函数里面跟新,中断函数里面跟新数据和标志位
		{
			数据处理,若用上位机查看波形,则加上帧格式发送
			开启DMA(关闭DMA、设置好传输数据大小、开启DMA)
			Receive_Flag=0(标志位清0,等待中断函数更新)
		}
	}

}

注意

1,u8一个字节比如0XAA,u16两个字节,short两个字节

stdint.h中
typedef   signed          char int8_t;
typedef   signed short     int int16_t;
typedef   signed           int int32_t;
typedef   signed       __int64 int64_t;
typedef unsigned          char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;
typedef unsigned       __int64 uint64_t;
stm32f10x.h中
typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8

2,USARTX->DR中的数据位是0~8共9位,一般都是一个一个字节的发送
在这里插入图片描述
所以DMA传输的数据格式和大小一定要注意

3,DMA中数组的定义用了volatile修饰词,因为这样可以保证每次的读取都是从绝对地址读出来的值,不会因为被会编译器进行优化导致读取到的值不是实时的AD值。

4,int a[3][4];
数组a有3个元素分别为a[0],a[1],a[2]
a[0]是第一行的四个元素的首地址,
a[1]是第二行的四个元素的首地址

5,

volatile uint16 ADCValue[10][3];//用来存放ADC转换结果,也是DMA的目标地址,3通道,每通道采集10次后面取平均数
。。。。。。。。。
while(1)
	{
		for(i=0;i<3;i<++)
		{
			sum=0;
			for(j=0;j<10;j++)
			{
				sum+=ADCConvertedValue[j][i];
			}
			ADC_Value[i]=(float)sum/(10*4096)*3.3;//求平均值并转换成电压值
			//打印(略)
		}
		//延时(略)
	}

匿名上位机

由于之前2020电赛中有涉及到用ADS1292画心电图,其中该模块可以单独发送数据到匿名上位机,在上位机显示波形,下面稍微介绍一下匿名上位机。

匿名上位机是用来调试四轴,也可以用来显示自己想要查看的波形,具有基本收发功能,包括串口调试、网络助手调试等,其最大一个特点就是波形的显示,在进行PID调参时,可以根据波形的走向趋势来设定参数,可以很直观的看到是否超调、振荡。

回归主题,串口发送的数据可以在串口调试助手查看其16进制的形式,这里需要对匿名上位机的帧格式有所了解,有数据帧、帧功能、数据长度、数据位等等,在上位机软件中查看通信协议,不同版本上位机协议有些不同。

比如,飞控显示对应的帧FUN为0xAF,(帧格式:0x88+0xAF+0x1C+ACC DATA+GYRO DATA+MAG DATA+ANGLE DATA+ 0x00 0x00 + 0x00 0x00+SUM,共32字节,ACC/GYRO/MAG/ANGLE(roll/pitch/yaw)数据为int16格式,其中ANGLE的roll和pitch数据为实际值乘以100以后得到的整数值,yaw为乘以10以后得到的整数值,上位机在显示时再 除以100和10)

下位机发送的数据要按照帧格式来发送给上位机,格式为:0x88+FUN+LEN+DATA+SUM,FUN可以是 0xA1到0xAA,共10个;LEN为DATA的长度(不包括0x88、FUN、LEN、SUM)。SUM是0x88一直到DATA最后一字节的和,uint8格式。(记得打开需要使用帧的开关,更改设置后点击保存设置使设置生效)
如下图12个数据,12/2=6,就是aacx,aacy,aacz,gyrox,gyroy,gyroz,1个数据16位2个字节(short两个字节16位),发送还是8位8位的发送
在这里插入图片描述
Mpu6050的发送数据给上位机也是同理

//串口1发送1个字符 
//c:要发送的字符
void usart1_send_char(u8 c)
{   	
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   
	USART_SendData(USART1,c);  
} 
//传送数据给匿名四轴上位机软件(V2.6版本)
//fun:功能字. 0XA0~0XAF
//data:数据缓存区,最多28字节!!
//len:data区有效数据个数
void usart1_niming_report(u8 fun,u8*data,u8 len)
{
	u8 send_buf[32];
	u8 i;
	if(len>28)return;	//最多28字节数据 
	send_buf[len+3]=0;	//校验数置零
	send_buf[0]=0X88;	//帧头
	send_buf[1]=fun;	//功能字
	send_buf[2]=len;	//数据长度
	for(i=0;i<len;i++)send_buf[3+i]=data[i];			//复制数据
	for(i=0;i<len+3;i++)send_buf[len+3]+=send_buf[i];	//计算校验和	
	for(i=0;i<len+4;i++)usart1_send_char(send_buf[i]);	//发送数据到串口1 
}
//发送加速度传感器数据和陀螺仪数据
//aacx,aacy,aacz:x,y,z三个方向上面的加速度值
//gyrox,gyroy,gyroz:x,y,z三个方向上面的陀螺仪值
void mpu6050_send_data(short aacx,short aacy,short aacz,short gyrox,short gyroy,short gyroz)
{
	u8 tbuf[12];
	tbuf[0]=(aacx>>8)&0XFF;
	tbuf[1]=aacx&0XFF;
	tbuf[2]=(aacy>>8)&0XFF;
	tbuf[3]=aacy&0XFF;
	tbuf[4]=(aacz>>8)&0XFF;
	tbuf[5]=aacz&0XFF; 
	tbuf[6]=(gyrox>>8)&0XFF;
	tbuf[7]=gyrox&0XFF;
	tbuf[8]=(gyroy>>8)&0XFF;
	tbuf[9]=gyroy&0XFF;
	tbuf[10]=(gyroz>>8)&0XFF;
	tbuf[11]=gyroz&0XFF;
	usart1_niming_report(0XA1,tbuf,12);//自定义帧,0XA1
}	
//通过串口1上报结算后的姿态数据给电脑
//aacx,aacy,aacz:x,y,z三个方向上面的加速度值
//gyrox,gyroy,gyroz:x,y,z三个方向上面的陀螺仪值
//roll:横滚角.单位0.01度。 -18000 -> 18000 对应 -180.00  ->  180.00度
//pitch:俯仰角.单位 0.01度。-9000 - 9000 对应 -90.00 -> 90.00 度
//yaw:航向角.单位为0.1度 0 -> 3600  对应 0 -> 360.0度
void usart1_report_imu(short aacx,short aacy,short aacz,short gyrox,short gyroy,short gyroz,short roll,short pitch,short yaw)
{
	u8 tbuf[28]; 
	u8 i;
	for(i=0;i<28;i++)tbuf[i]=0;//清0
	tbuf[0]=(aacx>>8)&0XFF;
	tbuf[1]=aacx&0XFF;
	tbuf[2]=(aacy>>8)&0XFF;
	tbuf[3]=aacy&0XFF;
	tbuf[4]=(aacz>>8)&0XFF;
	tbuf[5]=aacz&0XFF; 
	tbuf[6]=(gyrox>>8)&0XFF;
	tbuf[7]=gyrox&0XFF;
	tbuf[8]=(gyroy>>8)&0XFF;
	tbuf[9]=gyroy&0XFF;
	tbuf[10]=(gyroz>>8)&0XFF;
	tbuf[11]=gyroz&0XFF;	
	tbuf[18]=(roll>>8)&0XFF;
	tbuf[19]=roll&0XFF;
	tbuf[20]=(pitch>>8)&0XFF;
	tbuf[21]=pitch&0XFF;
	tbuf[22]=(yaw>>8)&0XFF;
	tbuf[23]=yaw&0XFF;
	usart1_niming_report(0XAF,tbuf,28);//飞控显示帧,0XAF
}  

小结:这次介绍的东西有点小乱,但都是这段时间遇到过的问题,都是围绕单片机从数据采集到处理然后传送的问题。

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值