9、DMA&USART&I2C&SPI(STM32)

 

1、DMA直接存储器存取—DMA转运数据/DMA+AD多通道

http://t.csdnimg.cn/fauUzhttp://t.csdnimg.cn/fauUz

下面是自己的学习版代码,多看注释,都是精华

认识DMA库函数

DMA库函数认识
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);//恢复缺省配置
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);//初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);//结构体初始化
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);//使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);//中断输出使能
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); //DMA设置当前数据寄存器(给传输计数器写数据)
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);//DMA获取当前数据寄存器(返回传输计时器的值,可以看看还有多少数据没有转运)
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG);//清除标志位
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);//获取中断状态
void DMA_ClearITPendingBit(uint32_t DMAy_IT);//清除中断挂起位

1.1、DMA数据转运

MyDMA.c

#include "stm32f10x.h"                  // Device header
//初始化DMA
uint16_t MyDMA_Size;

//第一步,RCC开启DMA时钟
//第二步,直接调用DMA_Init(),初始化各个参数(包括外设和存储器站点的起始地址、数据宽度、地址是否自增,方向、传输计时器、是否需要自动重装、选择触发源M2M、还有个通道优先级(图里没画))

//第三步,就开关控制,DMA_Cmd,给指定的通道使能
//在这里,如果选择硬件触发,不要忘记在对应的外设调用一下XXX_DMACmd,开启一下触发信号的输出
//如果需要DMA的中断,那就调用DMA_ITConfig,开启中断输出,再在NVIC里,配置相应的中断通道,写中断函数(中断的配置图没有,各个外设一样,参考其他的图就好)
//第四步,在运行的过程中,如果转运完成,传输计时器清零了,这时想再给传输计时器赋值的话,就DMA失能、写传输计时器、DMA使能,这样子就好

//DMA库函数认识
//void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);//恢复缺省配置
//void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);//初始化
//void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);//结构体初始化
//void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);//使能
//void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);//中断输出使能
//void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); //DMA设置当前数据寄存器(给传输计数器写数据)
//uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);//DMA获取当前数据寄存器(返回传输计时器的值,可以看看还有多少数据没有转运)
//FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取标志位状态
//void DMA_ClearFlag(uint32_t DMAy_FLAG);//清除标志位
//ITStatus DMA_GetITStatus(uint32_t DMAy_IT);//获取中断状态
//void DMA_ClearITPendingBit(uint32_t DMAy_IT);//清除中断挂起位



void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;//让Size传一份给全局变量MyDMA_Size,然后给后面的函数用
	
	//开启时钟,DMA时AHB总线的设备,用AHB开启时钟的函数
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	//第二步,直接调用DMA_Init(),初始化各个参数(包括外设和存储器站点的起始地址、数据宽度、地址是否自增,方向、传输计时器、是否需要自动重装、选择触发源M2M、还有个通道优先级(图里没画))
	
	//外设站点三个参数
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设站点的起始地址(基地址)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设站点的数据宽度:选择字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//外设站点是否自增:这种数组中间的转运是要自增的,不然会数据覆盖,所以选自增

	//存储器站点的三个参数同理
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储器站点的起始地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器站点的数据宽度
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器是否自增

	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向:DST外设站点作为目的地,传输方向是存储器到外设站点|SRC外设站点作为源头source,即传输方向为外设站点到存储器站点的传输方向,选SRC
	DMA_InitStructure.DMA_BufferSize = Size;//缓存器的大小(传输计数器)以数据单元,指定缓存区大小,这个数据单元=外设数据宽度或者存储器数据宽度,取决于传输方向
											//以数据单元指定缓冲区大小,就是说你要传送几个数据单元,这个数据单元等于你传输源端站点的DataSize
											//再简单点就是,BufferSize就是传输计时器,指定传输几次
											//在DMA_Init函数的源码,这个BufferSize参数,其实就是直接赋值给了传输计数器的寄存器取值是0到65535,
											//把BufferSize也提取到函数的参数里来,即uint16_t Size,然后把Size放到BufferSize参数里去
	
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式(是否使用自动重装)(注意:自动重装不能应用在存储器到存储器的情况下,也就是循环模式和软件触发模式不能同时使用,如果同时使用,DMA就会连续触发,永远也不会停下来)
												 //参数选择有两个:Circular,传输计数器自动重装;Normal,正常模式不重装,自减到0后停下来。存储器转运到存储器,转运一次就可以停下来了
												 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//M2M选择是否是存储器到存储器(其实就是选择硬件触发还是软件触发)
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
	
	//第一个参数可以选择DMA1的1到7通道,或者DMA2的1到5通道
	//这里因为是存储器到存储器的转运,用的是软件触发,所以可以DMA的任意通道
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, DISABLE);//不让DMA初始化后,就立刻进行转运,而是等调用后面的函数之后再转运
}

//MyDMA_Init()函数里:
//DMA_InitStructure.DMA_PeripheralBaseAddr = ;的参数要写一个32位的地址,比如0x20000000这样的地址,
//但对于SRAM的数组,他的地址是编译器分配的,并不是固定的,所以一般不会写绝对地址,而是通过数组名来获取地址
//则可把这个地址提取为初始化函数的参数,这样子想转运哪个数组,就把哪个数组的地址传进来就欧克了
//所以MyDMA_Init(void)里的参数void改为uint32_t AddrA,然后把AddrA放在外设基地址参数位置
//这样外设站点的地址就完成了


//DMA工作有三个条件:1、传输计数器大于0;第二个条件:触发源有触发信号;第三个条件,DMA使能


//DMA转运函数
void MyDMA_Transfer(void)
{
	//在这里面需要重新给传输计数器赋值,传输计数器赋值必须要先给DMA失能
	//DMA工作三条件
	DMA_Cmd(DMA1_Channel1, DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
	DMA_Cmd(DMA1_Channel1, ENABLE);//调用一次,转运一次
	
	//等待转运完成,转运也是要花时间的,用查看标志位函数加while循环(之前也这么用)
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//转运完后标志位置1,转运完成,下面手动清除标志位
	DMA_ClearFlag(DMA1_FLAG_TC1);//清除标志位
}

 MyDMA.h

#ifndef __MYDMA_H
#define __MYDMA_H

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
//这个代码的任务:初始化DMA,让DMA把DataA的数据转运到DataB
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};//源端数组
uint8_t DataB[] = {0, 0, 0, 0};//目标数组
//uint8_t DataB = 0x66;
int main(void)
{
	OLED_Init();
	//给它传输数组的地址(数组名就是地址,不用加地址符号)
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//有四个数据,所以传输数据次数为4
	
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
	OLED_ShowHexNum(4, 8, (uint32_t)&DataB, 8);//取地址	
	while (1)
	{
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);
	}
}

 1.2、DMA+AD多通道

程序执行流程图:

5c04228aee074208a05d07c09ed4df48.png

AD.c

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
	//开启ADC1,GPIOA,DMA1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//ADC点四个“菜”
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
	
	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式了
	ADC_InitStructure.ADC_NbrOfChannel = 4;//点四个菜,扫四个
	ADC_Init(ADC1, &ADC_InitStructure);
	
	//前面ADC厨师已经把“菜”做好,DMA要尽快把“菜”端出来,防止被覆盖,
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//厨师把菜做好放入ADC_DR寄存器里,所以这里填端菜的源头地址就填ADC_DR,对ADC->DR取地址,再强制类型转换为需要的uint32_t数据类型(类似4001244C)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//要转运低于16位的数据,就用半字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//不自增,始终转运同一个位置的数据
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//(对AD_Value数组的地址强制类型转换)
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 4;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//不使用软件触发,触发源位ADC1
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

AD.h

#ifndef __AD_H
#define __AD_H

extern uint16_t AD_Value[4];//extern表示可调用的数组

void AD_Init(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

int main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");
	
	while (1)
	{
		OLED_ShowNum(1, 5, AD_Value[0], 4);
		OLED_ShowNum(2, 5, AD_Value[1], 4);
		OLED_ShowNum(3, 5, AD_Value[2], 4);
		OLED_ShowNum(4, 5, AD_Value[3], 4);
		
		Delay_ms(100);
	}
}

2、串口

2.1、USART串口协议、串口外设—数据发送/数据发送+接收

http://t.csdnimg.cn/QFLmLhttp://t.csdnimg.cn/QFLmL

2.2、串口收发数据包—串口收发HEX数据包/串口收发文本数据包

http://t.csdnimg.cn/fKyvxhttp://t.csdnimg.cn/fKyvx

3、I2C

3.1、I2C通信协议—软件I2C读写MPU6050

http://t.csdnimg.cn/jnGsVhttp://t.csdnimg.cn/jnGsV

3.2、I2C外设总线—硬件I2C读写MPU6050

http://t.csdnimg.cn/uz2FPhttp://t.csdnimg.cn/uz2FP

4、SPI

4.1、SPI通信协议/W25Q64简介—软件SPI读写W25Q64

http://t.csdnimg.cn/7Fmnzhttp://t.csdnimg.cn/7Fmnz

4.2、SPI通信外设

http://t.csdnimg.cn/PazYxhttp://t.csdnimg.cn/PazYx

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值