1.前言
因为后面需要用到大量基础通讯传输的问题,于是今天折腾了一下DMA传输I2C与SPI的效果,其实我先是把DMA+SPI搞出来了。但是考虑到网上对于STM32的I2C微词颇多,基础的协议都没有调试出来,更遑论DMA控制了,前面调不出来我想找资料都找不到,还是先把I2C发出来吧,稍晚再发SPI的吧。
2.理论
2.1 I2C控制器端
这是手册上对于控制器端设置的描述,我们的关注点首先是第二行,DMAEN位的设置问题,在7位地址发送出来前就要进行置为,这一点我们一会能在程序里看到,这基本是发送时需要注意的点。
然后是接收,最后一两行写的比较清楚。我们需要用到传输完成中断,这里手册上描述的略有歧义,这里的中断是DMA侧的,而非I2C的中断。
2.2 传输时DMA配置
配置方式和普通的存储器到外设的配置方式大同小异,这里大家有不懂的可以详细看看
2.3 发送时DMA配置
与发送时的配置类似,这里大家也稍微看看
3.程序
本次实验里,我设置I2C是主模式,从模式对单片机端用的非常少,然后是通讯速率,我设置的是100Khz,我这里以I2C1作为介绍了。此外我没有写传输单个数据的程序,因为考虑到DMA是大量数据下的优化方式,并且单个数据的传输配置非常麻烦,所以我就没写了。
额外提一嘴,因为我对于407的DMA和I2C用的非常多了,下面的内容我不会再一个接着一个寄存器讲了,大家有不懂的可以看看我前面的文章,尤其是我上一篇对于ST的I2C控制器的完整介绍,本次实验也是在那个程序上修改得出的。
3.1 I2C控制器初始化
首先我先把程序放出来
/*
注意,I2C一定要加上超时的设置,否则当IIC总线出错时,
没有超时检测可能造成MCU卡死在这里。
*/
void init_i2c(void)
{
RCC->APB1ENR|=1<<21; //使能时钟
//初始化端口
GPIOB->MODER|=2<<16;
GPIOB->MODER|=2<<18;
GPIOB->OSPEEDR|=1<<16;
GPIOB->OSPEEDR|=1<<18;
//!!!一定要设置成开漏输出模式!!!
GPIOB->OTYPER|=1<<8;
GPIOB->OTYPER|=1<<9;
GPIOB->AFR[1]|=4<<0;
GPIOB->AFR[1]|=4<<4;
//设置I2C主频与通信速率
I2C1->CR2|=0x2A;
I2C1->TRISE=0x2B;
I2C1->CCR|=0xD2;
I2C1->CR1&=~(1<<1); //I2C模式
I2C1->CR1|=1<<7; //禁止时钟延长
I2C1->CR2|=1<<8; //开启错误中断
#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
I2C1->CR2|=1<<11; //开启I2C发送DMA
I2C1->CR2|=1<<12; //设置DMA传输最后一位关闭应答
#endif
I2C1->CR1|=0x01; //开启I2C通信
}
可以看到和前面的区别不大,我把改动的点拿出来说一下。主要是在下面这两个设置,I2C1的CR2寄存器里的11和12位置。
#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
I2C1->CR2|=1<<11; //开启I2C发送DMA
I2C1->CR2|=1<<12; //设置DMA传输最后一位关闭应答
#endif
这两位是啥呢?
首先是11位,这是DMA使能的开关,不必多说了,想用DMA是必开的。
然后是12位,什么是最后一次DMA传输?我们重新看一下上面理论里面对这个的描述
当LAST置1时,会硬件在最后一位置NACK,当然,这是在接收模式下。记得不用DMA时I2C的传输方式不?我们需要手动控制芯片是否输出ACK,并且需要在最后一位到来前把ACK位清空。有了这个控制位,我们再也不要判断了!!并且可以在进入传输第一个数据时就可以置位,芯片会自行进行应答配置!!(我的天,这才是该有的舒适感!)
3.2 传输时DMA设置
首先我们确定DMA的通道,这次实验里我发送是用DMA1的数据流7的通道1作为发送端
程序如下
//初始化DMA1 组7 通道1
//IIC1_TX
void init_DMA1_S7C1(unsigned char *IICData,unsigned short IICWEI)
{
DMA1_Stream7 ->CR = 0;//禁止数据流 ,才能写寄存器
//外设地址寄存器
//将所需寄存器的地址放入PAR寄存器
DMA1_Stream7 ->PAR = (unsigned int)(&I2C1->DR);
//数据流地址寄存器
//M1AR仅在双通道模式下有用
//将数据所在地址给M0AR寄存器
DMA1_Stream7 ->M0AR = (unsigned int)(IICData);
DMA1_Stream7 ->NDTR = IICWEI; // 一次传输数量
DMA1_Stream7 ->FCR = 0x21; //FIFO所有配置失效
DMA1_Stream7 ->CR |= 1<< 6; //储存器到外设模式
//循环模式:
//当NDTR寄存器减到0时自动重装
//单次模式(普通模式):
//NDTR减到0后停止DMA
DMA1_Stream7 ->CR &=~(1<<8); //非循环模式
DMA1_Stream7 ->CR &=~(3<<11); //外设数据长度:8位
DMA1_Stream7 ->CR &=~(3<<13); //存储器数据长度:8位
DMA1_Stream7 ->CR &= ~(1<<9); //外设非增量模式
DMA1_Stream7 ->CR |= 1<<10; //存储器增量模式,指针增加,可用于传输数组
DMA1_Stream7 ->CR |= 1<<16; //中等优先级
//突发传输
//DMA占用CPU总线时间,此时CPU无法工作
//一个节拍:传输多少次32位变量
//应用场景:从ram里读出字节
DMA1_Stream7 ->CR &= ~(3<<21); //外设突发单次传输
DMA1_Stream7 ->CR &= ~(3<<23); //存储器突发单次传输
DMA1_Stream7 ->CR |= 1<<25; //通道1
DMA1_Stream7 ->CR |= 1<<0; //使能数据流
}
和普通存储器到寄存器没什么区别,大家略微看看,没什么特殊设置的。
3.3 接收时DMA设置
接收的通道是DMA1数据流5通道1
程序如下
先是DMA初始化
//初始化DMA1 组5 通道1
//IIC1_RX
void init_DMA1_S5C1(unsigned char *IICData,unsigned short IICWEI)
{
DMA1_Stream5 ->CR = 0;//禁止数据流 ,才能写寄存器
DMA1_Stream5 ->CR |= 1<<4; //开启传输完成中断
DMA1->LIFCR |=0x3d<<6; //清空组5所有中断标志位
//外设地址寄存器
//将所需寄存器的地址放入PAR寄存器
DMA1_Stream5 ->PAR = (unsigned int)(&I2C1->DR);
//数据流地址寄存器
//M1AR仅在双通道模式下有用
//将数据所在地址给M0AR寄存器
DMA1_Stream5 ->M0AR = (unsigned int)(IICData);
DMA1_Stream5 ->NDTR = IICWEI; // 一次传输数量
DMA1_Stream5 ->FCR = 0x21; //FIFO所有配置失效
DMA1_Stream5 ->CR &= ~(3<<6); //外设到存储器模式
//循环模式:
//当NDTR寄存器减到0时自动重装
//单次模式(普通模式):
//NDTR减到0后停止DMA
DMA1_Stream5 ->CR &=~(1<<8); //非循环模式
DMA1_Stream5 ->CR &=~(3<<11); //外设数据长度:8位
DMA1_Stream5 ->CR &=~(3<<13); //存储器数据长度:8位
DMA1_Stream5 ->CR &= ~(1<<9); //外设非增量模式
DMA1_Stream5 ->CR |= 1<<10; //存储器增量模式,指针增加,可用于传输数组
DMA1_Stream5 ->CR |= 1<<16; //中等优先级
//突发传输
//DMA占用CPU总线时间,此时CPU无法工作
//一个节拍:传输多少次32位变量
//应用场景:从ram里读出字节
DMA1_Stream5 ->CR &= ~(3<<21); //外设突发单次传输
DMA1_Stream5 ->CR &= ~(3<<23); //存储器突发单次传输
DMA1_Stream5 ->CR |= 1<<25; //通道1
DMA1_Stream5 ->CR |= 1<<0; //使能数据流
DMA1S5_InitInterrupt();
DMA1S5_Subpriority();
}
中断服务函数如下
void DMA1_Stream5_IRQHandler(void)
{
if(DMA1->HISR&(1<<27))
{
DMA1->HIFCR|=(1<<27);
}
if(DMA1->HISR&(1<<11))
{
I2C1->CR1|=1<<9;
DMA1->HIFCR|=(1<<11);
}
}
我们先从初始化程序来看,唯一要变的是启用中断,记得开启传输完成中断就行。这里的中断还是比较好用的,如果大家写过串口的,那个需要用到空闲中断,这里就是普通的传输完成中断,没什么特别的。
此外我们在开启中断后需要对所有状态位进行清空,否则开启中断后会默认置位了,程序会卡bug并且需要先开启中断再清空。
然后是中断服务函数,因为前面我用DAC,而DAC是相同数据流,不过是通道7。所以服务函数里面我有两个,大家如果没有需要可以只保留这一段。这里为什么会对I2C进行设置?
我们看看手册,第9位是停止位,给这位置1可以在接收完成后补上停止信号。
3.4 发送程序
这是单次发送多个数据的程序
void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
unsigned int wei=0;
I2C1->CR1|=1<<8; //发出起始信号
while((I2C1->SR1&(1<<0))==0); //等待起始信号发送完毕
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1); //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
while((I2C1->SR1&(1<<1))==0); //等待地址发送完毕
I2CEV(6);
for(wei=0;wei<=length;wei++)
{
while((I2C1->SR1&(1<<7))==0); //等待数据发送完毕
#if I2C_DMA_TX_EN==0
I2C1->DR=i2cdata[wei]; //EV8
#endif
}
I2C1->CR1|=1<<9; //发送停止位
}
讲一下核心区别
其他的基本不用变,核心变化在这里,原本是CPU控制发送数据,这里可以不用了,当TxE(即SR1的第7位)为1时会产生一个DMA请求,DMA直接搬一个数据到DR寄存器了。我们只要保证数据没用发生覆盖和移除就行,这也就是只保留上一句的原因:保证数据完整地发出去了。
3.5 接收程序
这是接收多个数据的函数(注意不是收寄存器的!!收寄存器需要发送,我后面会写)
void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
unsigned int wei=0;
I2C1->CR1|=1<<8; //发出起始信号
while((I2C1->SR1&(1<<0))==0); //等待起始信号发送完毕
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1)+1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
while((I2C1->SR1&(1<<1))==0); //等待地址发送完毕
I2CEV(6);
for(wei=0;wei<=length;wei++)
{
if(length!=1){I2C1->CR1|=1<<10;} //多重数据接收时由主机应答
#if I2C_DMA_RX_EN==0
if(wei==length-1)
{
I2C1->CR1&=~(1<<10); //最后一位无需应答
I2C1->CR1|=1<<9; //提前写入停止位
}
while((I2C1->SR1&(1<<6))==0); //等待数据接收完毕
i2cdata[wei]=I2C1->DR; //EV8
#endif
}
}
与发送同理
不过需要的内容更少了,我们甚至不需要等待RxE,我们只需要提前把主机应答的信号打开,最后一个NACK也是自动产生的。(简直不要太幸福!!)
4.测试
4.1 所有程序
首先是完整程序
i2c.c
#include "iic.h"
/*
注意,I2C一定要加上超时的设置,否则当IIC总线出错时,
没有超时检测可能造成MCU卡死在这里。
*/
void init_i2c(void)
{
RCC->APB1ENR|=1<<21; //使能时钟
//初始化端口
GPIOB->MODER|=2<<16;
GPIOB->MODER|=2<<18;
GPIOB->OSPEEDR|=1<<16;
GPIOB->OSPEEDR|=1<<18;
//!!!一定要设置成开漏输出模式!!!
GPIOB->OTYPER|=1<<8;
GPIOB->OTYPER|=1<<9;
GPIOB->AFR[1]|=4<<0;
GPIOB->AFR[1]|=4<<4;
//设置I2C主频与通信速率
I2C1->CR2|=0x2A;
I2C1->TRISE=0x2B;
I2C1->CCR|=0xD2;
I2C1->CR1&=~(1<<1); //I2C模式
I2C1->CR1|=1<<7; //禁止时钟延长
I2C1->CR2|=1<<8; //开启错误中断
#if I2C_DMA_TX_EN==1||I2C_DMA_RX_EN==1
I2C1->CR2|=1<<11; //开启I2C发送DMA
I2C1->CR2|=1<<12; //设置DMA传输最后一位关闭应答
#endif
I2C1->CR1|=0x01; //开启I2C通信
}
void I2CEV(unsigned char EVin)
{
unsigned char EVTemp;
switch(EVin)
{
case 5:
EVTemp=I2C1->SR1; //读取sr1寄存器
break;
case 6:
EVTemp=I2C1->SR1; //读取sr1寄存器
EVTemp=I2C1->SR2; //读取sr2寄存器
break;
case 8:
;
break;
}
}
void i2c_WriteOneChar(unsigned char i2c_address,unsigned char i2cin)
{
unsigned int wait;
I2C1->CR1|=1<<8; //发出起始信号
//等待起始信号发送完毕
for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=i2c_address<<1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
//等待地址发送完毕
for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(6);
I2C1->DR=i2cin; //EV8
//等待数据发送完毕
for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2C1->CR1|=1<<9; //写入停止位
}
unsigned char i2c_ReadOneChar(unsigned char i2c_address)
{
unsigned char readtemp;
unsigned char addresstemp;
I2C1->CR1|=1<<8; //发出起始信号
while((I2C1->SR1&(1<<0))==0); //等待起始信号发送完毕
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1)+1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
while((I2C1->SR1&(1<<1))==0); //等待地址发送完毕
I2CEV(6);
readtemp=I2C1->DR; //EV7
while((I2C1->SR1&(1<<6))==0); //等待数据接收完毕
I2C1->CR1|=1<<9; //写入停止位
return readtemp;
}
void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
unsigned int wei=0;
I2C1->CR1|=1<<8; //发出起始信号
while((I2C1->SR1&(1<<0))==0); //等待起始信号发送完毕
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1); //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
while((I2C1->SR1&(1<<1))==0); //等待地址发送完毕
I2CEV(6);
for(wei=0;wei<=length;wei++)
{
while((I2C1->SR1&(1<<7))==0); //等待数据发送完毕
#if I2C_DMA_TX_EN==0
I2C1->DR=i2cdata[wei]; //EV8
#endif
}
I2C1->CR1|=1<<9; //发送停止位
}
void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length)
{
unsigned int wei=0;
I2C1->CR1|=1<<8; //发出起始信号
while((I2C1->SR1&(1<<0))==0); //等待起始信号发送完毕
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1)+1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
while((I2C1->SR1&(1<<1))==0); //等待地址发送完毕
I2CEV(6);
for(wei=0;wei<=length;wei++)
{
if(length!=1){I2C1->CR1|=1<<10;} //多重数据接收时由主机应答
#if I2C_DMA_RX_EN==0
if(wei==length-1)
{
I2C1->CR1&=~(1<<10); //最后一位无需应答
I2C1->CR1|=1<<9; //提前写入停止位
}
while((I2C1->SR1&(1<<6))==0); //等待数据接收完毕
i2cdata[wei]=I2C1->DR; //EV8
#endif
}
}
void i2c_ReadRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength)
{
unsigned int wait;
unsigned int wei=0;
//发送
I2C1->CR1|=1<<8; //发出起始信号
//等待起始信号发送完毕
for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(5); //EV5
#if ADDRESSBIT==7
I2C1->DR=i2c_address<<1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
//等待地址发送完毕
for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(6);
for(wei=0;wei<registlength;wei++)
{
#if I2C_DMA_TX_EN==1
#else
I2C1->DR=regaddress[wei]; //EV8
#endif
//等待数据发送完毕
for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
}
//接收
I2C1->CR1|=1<<8; //发出起始信号
//等待起始信号发送完毕
for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(5);
#if ADDRESSBIT==7
I2C1->DR=(i2c_address<<1)+1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
//等待地址发送完毕
for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(6);
for(wei=0;wei<datalength;wei++)
{
if(datalength!=1){I2C1->CR1|=1<<10;} //多重数据接收时由主机应答
#if I2C_DMA_RX_EN==0
if(wei==datalength-1)
{
I2C1->CR1&=~(1<<10); //最后一位无需应答
I2C1->CR1|=1<<9; //提前写入停止位
}
//等待数据接收完毕
for(wait=0;(I2C1->SR1&(1<<6))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
i2cdata[wei]=I2C1->DR; //EV8
#endif
}
}
void i2c_WriteRegist(unsigned char i2c_address,unsigned char *i2cdata,unsigned char datalenght)
{
unsigned int wait;
unsigned char wei=0;
//发送
I2C1->CR1|=1<<8; //发出起始信号
//等待起始信号发送完毕
for(wait=0;(I2C1->SR1&(1<<0))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(5); //EV5
#if ADDRESSBIT==7
I2C1->DR=i2c_address<<1; //写入地址
#else
I2C1->DR=i2c_address; //写入地址
#endif
//等待地址发送完毕
for(wait=0;(I2C1->SR1&(1<<1))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
I2CEV(6);
for(wei=0;wei<datalenght;wei++)
{
I2C1->DR=i2cdata[wei]; //EV8
//等待数据发送完毕
for(wait=0;(I2C1->SR1&(1<<7))==0;wait++){if(wait>WAITTIME){I2C1->CR1|=1<<9;return;}}
}
I2C1->CR1|=1<<9; //写入停止位
}
i2c.h
#ifndef __IIC_H
#define __IIC_H
#include "stm32f4xx.h"
//I2C数据长度
#define ADDRESSBIT 7
//I2C等待时间
#define WAITTIME 5000
//I2C传输是否开启DMA功能
#define I2C_DMA_TX_EN 1
//I2C接收是否开启DMA功能
#define I2C_DMA_RX_EN 1
void init_i2c(void);
void i2c_WriteOneChar(unsigned char i2c_address,unsigned char i2cin);
unsigned char i2c_ReadOneChar(unsigned char i2c_address);
void i2c_MultiWrite(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length);
void i2c_MultiRead(unsigned char i2c_address,unsigned char* i2cdata,unsigned char length);
void i2c_ReadRegist(unsigned char i2c_address,unsigned char *regaddress,unsigned char registlength,unsigned char* i2cdata,unsigned char datalength);
#endif
DMA和中断
//初始化DMA1 组7 通道1
//IIC1_TX
void init_DMA1_S7C1(unsigned char *IICData,unsigned short IICWEI)
{
DMA1_Stream7 ->CR = 0;//禁止数据流 ,才能写寄存器
//外设地址寄存器
//将所需寄存器的地址放入PAR寄存器
DMA1_Stream7 ->PAR = (unsigned int)(&I2C1->DR);
//数据流地址寄存器
//M1AR仅在双通道模式下有用
//将数据所在地址给M0AR寄存器
DMA1_Stream7 ->M0AR = (unsigned int)(IICData);
DMA1_Stream7 ->NDTR = IICWEI; // 一次传输数量
DMA1_Stream7 ->FCR = 0x21; //FIFO所有配置失效
DMA1_Stream7 ->CR |= 1<< 6; //储存器到外设模式
//循环模式:
//当NDTR寄存器减到0时自动重装
//单次模式(普通模式):
//NDTR减到0后停止DMA
DMA1_Stream7 ->CR &=~(1<<8); //非循环模式
DMA1_Stream7 ->CR &=~(3<<11); //外设数据长度:8位
DMA1_Stream7 ->CR &=~(3<<13); //存储器数据长度:8位
DMA1_Stream7 ->CR &= ~(1<<9); //外设非增量模式
DMA1_Stream7 ->CR |= 1<<10; //存储器增量模式,指针增加,可用于传输数组
DMA1_Stream7 ->CR |= 1<<16; //中等优先级
//突发传输
//DMA占用CPU总线时间,此时CPU无法工作
//一个节拍:传输多少次32位变量
//应用场景:从ram里读出字节
DMA1_Stream7 ->CR &= ~(3<<21); //外设突发单次传输
DMA1_Stream7 ->CR &= ~(3<<23); //存储器突发单次传输
DMA1_Stream7 ->CR |= 1<<25; //通道1
DMA1_Stream7 ->CR |= 1<<0; //使能数据流
}
//初始化DMA1 组5 通道1
//IIC1_RX
void init_DMA1_S5C1(unsigned char *IICData,unsigned short IICWEI)
{
DMA1_Stream5 ->CR = 0;//禁止数据流 ,才能写寄存器
DMA1_Stream5 ->CR |= 1<<4; //开启传输完成中断
DMA1->LIFCR |=0x3d<<6; //清空组5所有中断标志位
//外设地址寄存器
//将所需寄存器的地址放入PAR寄存器
DMA1_Stream5 ->PAR = (unsigned int)(&I2C1->DR);
//数据流地址寄存器
//M1AR仅在双通道模式下有用
//将数据所在地址给M0AR寄存器
DMA1_Stream5 ->M0AR = (unsigned int)(IICData);
DMA1_Stream5 ->NDTR = IICWEI; // 一次传输数量
DMA1_Stream5 ->FCR = 0x21; //FIFO所有配置失效
DMA1_Stream5 ->CR &= ~(3<<6); //外设到存储器模式
//循环模式:
//当NDTR寄存器减到0时自动重装
//单次模式(普通模式):
//NDTR减到0后停止DMA
DMA1_Stream5 ->CR &=~(1<<8); //非循环模式
DMA1_Stream5 ->CR &=~(3<<11); //外设数据长度:8位
DMA1_Stream5 ->CR &=~(3<<13); //存储器数据长度:8位
DMA1_Stream5 ->CR &= ~(1<<9); //外设非增量模式
DMA1_Stream5 ->CR |= 1<<10; //存储器增量模式,指针增加,可用于传输数组
DMA1_Stream5 ->CR |= 1<<16; //中等优先级
//突发传输
//DMA占用CPU总线时间,此时CPU无法工作
//一个节拍:传输多少次32位变量
//应用场景:从ram里读出字节
DMA1_Stream5 ->CR &= ~(3<<21); //外设突发单次传输
DMA1_Stream5 ->CR &= ~(3<<23); //存储器突发单次传输
DMA1_Stream5 ->CR |= 1<<25; //通道1
DMA1_Stream5 ->CR |= 1<<0; //使能数据流
DMA1S5_InitInterrupt();
DMA1S5_Subpriority();
}
void DMA1_Stream5_IRQHandler(void)
{
if(DMA1->HISR&(1<<27))
{
DMA1->HIFCR|=(1<<27);
}
if(DMA1->HISR&(1<<11))
{
I2C1->CR1|=1<<9;
DMA1->HIFCR|=(1<<11);
}
}
4.2 多数据发送
首先是发送,初始化IIC,然后配置DMA,最后写好传输函数,这里MultiWrite里面后面两个参数与上面DMA里面的参数保持一致就行。
unsigned char iic_regist[2]={0x20,0x08};
init_i2c();//硬件IIC初始化
init_DMA1_S7C1(iic_regist,2);
i2c_MultiWrite(0x01,iic_data,2);//开启传输
我们发送两个数据0x20 0x08测试一下
波形非常漂亮,波形数据和程序里是一样的。
4.3 多数据接收
与发送类似
unsigned char iic_data[2];
init_i2c();//硬件IIC初始化
init_DMA1_S5C1(iic_data,3);//配置DMA
i2c_MultiRead(0x01,iic_data,3);//开启传输
4.4 寄存器接收
两者一结合就行了
unsigned char iic_data[2];
unsigned char iic_regist[2]={0x20,0x08};
init_DMA1_S7C1(iic_regist,2);
init_DMA1_S5C1(iic_data,3);
i2c_ReadRegist(0x01,iic_regist,2,iic_data,3);
没毛病
5.结语
今天搞的也算是了却一个心结了,这样的I2C完全碾压了模拟I2C。网上关于ST的I2C几乎处于以讹传讹的阶段,没有人研究过I2C底层操作。其实加上DMA后I2C更好操作了,传完地址后几乎不需要研究传输的过程,全都是芯片的事了。这两天有点事,稍晚我会把SPI发出来,争取早点做成视频传到B站上,让更多人用到吧。那么还是老样子,有问题评论区留言,我们下篇文章见。