打印
[RISC-V MCU 应用开发]

第四十二章、CH32V103应用教程——I2C-DMA,主机发送从机接收

[复制链接]
7374|15
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DMA, I2c, ni, ST, IO
本帖最后由 RISCVLAR 于 2021-1-5 20:46 编辑

CH32V103应用教程——I2C-DMA,主机发送从机接收

本章教程主要在前面第38章的基础上进行DMA模式下的主机发送从机接收实验。
注意,本章例程使用CH32V103硬件IIC。

1、I2C简介及相关函数介绍
关于I2C,可以使用DMA来进行批量数据的收发。使用DMA时不能对控制寄存器的 ITBUFEN 位进行置位。

l 利用DMA发送
通过控制寄存器的DMAEN位置位可以激活DMA模式。只要TxE位被置位,数据将由DMA从设定的内存装载进I2C的数据寄存器。需要进行以下设定来为I2C分配通道。
1)向DMA_PADDRx寄存器设置I2Cx_DATAR寄存器地址,DMA_MADDRx 寄存器中设置存储器地址,这样在每个TxE事件后,数据将从存储器送至 I2Cx_DATAR寄存器。
2)在DMA_CNTRx寄存器中设置所需的传输字节数。在每个TxE事件后,此值将被递减。
3)利用DMA_CFGRx寄存器中的PL[0:1]位配置通道优先级。
4)设置DMA_CFGRx寄存器中的DIR位,并根据应用要求可以配置在整个传输完成一半或全部完成时发出中断请求。
5)通过设置DMA_CFGRx寄存器上的EN位激活通道。
当DMA控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的EOT/ EOT_1信号。在中断允许的情况下,将产生一个 DMA 中断。

2利用 DMA 接收
置位DMAEN后即可进行DMA接收模式。使用DMA接收时,DMA将数据寄存器里的数据传送到预设的内存区域。需要以下步骤来为I2C分配通道。
1)向DMA_PADDRx寄存器设置I2Cx_DATAR寄存器地址,DMA_MADDRx 寄存器中设置存储器地址,这样在每个RxNE事件后,数据将从I2Cx_DATAR寄存器写入存储器。
2)在DMA_CNTRx寄存器中设置所需的传输字节数。在每个RxNE事件后,此值将被递减。
3)用DMA_CFGRx寄存器中的PL[0:1]配置通道优先级。
4)清除DMA_CFGRx寄存器中的DIR位,根据应用要求可以设置在数据传输完成一半或全部完成时发出中断请求。
5)设置 DMA_CFGRx 寄存器中的 EN 位激活该通道。
当 DMA 控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的EOT/EOT_1信号。在中断允许的情况下,将产生一个 DMA中断。

关于I2C其他相关介绍,在前面章节已经介绍,在此不再赘述。
关于CH32V103各模式介绍以及具体信息,可参考CH32V103应用手册。I2C标准库函数在第十四章节已介绍,在此不再赘述。

2、硬件设计
本章教程使用开发板硬件I2C进行主机发送从机接收实验,需用到两个开发板,将两个开发板对应IIC引脚连接起来即可。
注意:此处需要将开发板I2C引脚外接上拉电阻。

3软件设计
本章教程在前面第38章基础上使用硬件I2C在DMA模式下进行主机发送从机接收实验,具体程序如下:
iic.h文件
#ifndef __IIC_H
#define __IIC_H

#include "ch32v10x_conf.h"

/* I2C Mode Definition */
#define HOST_MODE   0
#define SLAVE_MODE   1

/* I2C Communication Mode Selection */
#define I2C_MODE   HOST_MODE
//#define I2C_MODE   SLAVE_MODE

/* Global define */
#define Size   7
#define Tize   6
#define RXAdderss   0x02
#define TxAdderss   0x02

void IIC_Init( u32 bound, u16 address );
void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize);
void DMA_Rx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize );
void DMA1_Channel6_IRQHandler();

#endif
iic.h文件主要进行宏定义和函数声明;
iic.c文件
#include "iic.h"

void DMA1_Channel6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*******************************************************************************
* Function Name  : IIC_Init
* Description    : Initializes the IIC peripheral.
* Input          : None
* Return         : None
*******************************************************************************/
void IIC_Init( u32 bound, u16 address )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitTSturcture;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE );
    GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); //重映射功能,PB8和PB9重映射为I2C1
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOB, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOB, &GPIO_InitStructure );

    I2C_InitTSturcture.I2C_ClockSpeed = bound;    //设置I2C传输速率
    I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;   //指定I2C工作模式
    I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_16_9; //指定时钟占空比
    I2C_InitTSturcture.I2C_OwnAddress1 = address; //指定I2C自身设备地址
    I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;  //使能或者关闭响应 (一般都是使能)
    I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //指定地址的长度,可以是7位或10位
    I2C_Init( I2C1, &I2C_InitTSturcture );

    I2C_DMACmd( I2C1, ENABLE );

    I2C_Cmd( I2C1, ENABLE );

#if (I2C_MODE == HOST_MODE)
    I2C_AcknowledgeConfig( I2C1, ENABLE );

#endif
}

/*******************************************************************************
* Function Name  : DMA_Tx_Init
* Description    : Initializes the SPI1 DMAy Channelx configuration.
* Input          : DMA_CHx:
*                  ppadr: Peripheral base address.
*                  memadr: Memory base address.
*                  bufsize: DMA channel buffer size.
* Return         : None
*******************************************************************************/
void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA1时钟

    DMA_DeInit(DMA_CHx);

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;    //设置外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;       //设置存储器地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;   //设置传输方向:存储器到外设
    DMA_InitStructure.DMA_BufferSize = bufsize;          //设置传输大小
    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工作模式为正常模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;  //设置DMA1通道6优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;             //禁止使能DMA存储器到存储器的传输方式
    DMA_Init( DMA_CHx, &DMA_InitStructure );

    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    DMA_ITConfig( DMA1_Channel6, DMA_IT_TC, ENABLE );
}

/*******************************************************************************
* Function Name  : DMA_Rx_Init
* Description    : Initializes the SPI1 DMAy Channelx configuration.
* Input          : DMA_CHx:
*                  ppadr: Peripheral base address.
*                  memadr: Memory base address.
*                  bufsize: DMA channel buffer size.
* Return         : None
*******************************************************************************/
void DMA_Rx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize )
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

    DMA_DeInit(DMA_CHx);

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    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_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init( DMA_CHx, &DMA_InitStructure );
}

/*******************************************************************************
* Function Name  : DMA1_Channel6_IRQHandler
* Description    : This function handles DMA1 channel6 exception.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void DMA1_Channel6_IRQHandler()
{
    if( DMA_GetITStatus(DMA1_IT_TC6) != RESET )
    {
        DMA_Cmd( DMA1_Channel6, DISABLE );
        DMA_ClearITPendingBit(DMA1_IT_TC6);
    }
}

iic.c文件主要对开发板硬件I2C进行初始化配置以及发送和接收DMA初始化配置。IIC_Init函数包括硬件I2C对应GPIO引脚配置以及指定I2C外设配置,其中I2C外设配置可结合ch32v10x_i2c.c文件中I2C_Init函数以及CH32V103应用手册中I2C主模式和从模式通讯步骤进行理解。DMA_Tx_Init函数是对发送端DMA进行初始化配置,DMA_Rx_Init函数是对接收端DMA进行初始化配置。DMA1_Channel6_IRQHandler函数是DMA1通道6中断服务函数。
当作为主机模式时,需要将指定I2C设备地址设置为主机地址,同时需要开启I2C响应设置。当作为从机模式时,需要将指定I2C设备地址设置为从机地址。
main.c文件
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2020/04/30
* Description        : Main program body.
*******************************************************************************/

#include "debug.h"
#include "iic.h"

/* Global Variable */
u8 TxData[Size] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
u8 RxData[Size];


/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
int main(void)
{

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);

#if (I2C_MODE == HOST_MODE)
    printf("IIC Host mode\r\n");

    //DMA初始化
    DMA_Tx_Init( DMA1_Channel6, (u32)&I2C1->DATAR, (u32)TxData, Tize );

    //I2C进行主机初始化配置
    IIC_Init( 80000, TxAdderss);

    //当I2C1处于空闲状态时,跳过此while循环,开启起始信号
    while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET ); //检查是否设置了指定I2C标志

    //开启I2C1通信起始信号
    I2C_GenerateSTART( I2C1, ENABLE );

    //当最后一个事件为I2C_EVENT_MASTER_MODE_SELECT事件时,说明选择I2C作为主机模式,跳过此while循环,进行下一步
    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) );  //检查最后一个I2Cx事件是否等于作为参数传递的事件

    //发送地址来选择从机设备
    I2C_Send7bitAddress( I2C1, 0x02, I2C_Direction_Transmitter );

    //当最后一个事件为I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED事件时,说明选择I2C进行主机发送,跳过此while循环,进行下一步数据发送
    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) );

    //使能开启DMA1通道6,进行数据发送
    DMA_Cmd( DMA1_Channel6, ENABLE );

    //当最后一个事件为I2C_EVENT_MASTER_BYTE_TRANSMITTED事件时,说明发送结束,跳过此while循环,进行下一步
    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );

    //开启I2C通信停止信号
    I2C_GenerateSTOP( I2C1, ENABLE );

#elif (I2C_MODE == SLAVE_MODE)

    u8 i=0;

    printf("IIC Slave mode\r\n");

    //DMA初始化
    DMA_Rx_Init( DMA1_Channel7, (u32)&I2C1->DATAR, (u32)RxData, Tize );

    //I2C进行从机初始化配置
    IIC_Init( 80000, RXAdderss);

    //当最后一个事件为I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED事件时,说明选择I2C作为从机进行数据接收,跳过此while循环,进行数据接收
    while( !I2C_CheckEvent( I2C1, I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED ) );

    //使能开启DMA1通道7进行数据接收
    DMA_Cmd( DMA1_Channel7, ENABLE );

    //当DMA1通道7传输完成,跳过此循环,开启下一步
    while( (!DMA_GetFlagStatus(DMA1_FLAG_TC7)) );

    printf( "RxData:\r\n" );

    for( i=0; i<6; i++ )
    {
        printf( "%02x\r\n", RxData[i] );
    }

#endif

    while(1);
}

main.c文件主要进行主机模式下的数据发送配置和从机模式下的数据接收配置。其中在7位地址模式下,发送的第一个字节为地址字节,头7位代表的是目标从设备地址,第8位决定了后续报文的方向,0代表是主设备写入数据到从设备,1代表是主设备向从设备读取信息。其中I2C_Send7bitAddress函数会根据第三个输入进行写入和读取的判断。

4下载验证
将编译好的程序分别在主机模式和从机模式下下载到两个开发版,并将两个开发板的PB8和PB9引脚接上拉电阻然后连接起来,将两个开发板进行复位,即可进行I2C通信,主机接收开发板串口打印情况具体如下:


41、IIC-DMA,主机发送从机接收.rar

487.84 KB

使用特权

评论回复

相关帖子

沙发
minzisc| | 2021-1-23 20:29 | 只看该作者
这个用法跟stm32一样的吗?

使用特权

评论回复
板凳
RISCVLAR|  楼主 | 2021-1-25 09:41 | 只看该作者
minzisc 发表于 2021-1-23 20:29
这个用法跟stm32一样的吗?

基本上一样

使用特权

评论回复
地板
jinhui10507| | 2021-8-13 14:30 | 只看该作者
手册上怎么找不到I2C的DMA通道,应该是通道几呢

使用特权

评论回复
5
RISCVLAR|  楼主 | 2021-8-13 16:37 | 只看该作者
jinhui10507 发表于 2021-8-13 14:30
手册上怎么找不到I2C的DMA通道,应该是通道几呢

可参考下图

使用特权

评论回复
6
jkl21| | 2021-10-5 09:50 | 只看该作者
I2C从机怎么设置的   

使用特权

评论回复
7
iyoum| | 2021-10-5 09:50 | 只看该作者
DMA模式有长度限制吗   

使用特权

评论回复
8
uytyu| | 2021-10-5 09:51 | 只看该作者
批量数据的收发可以。      

使用特权

评论回复
9
myiclife| | 2021-10-5 09:51 | 只看该作者
数据存储是怎么实现的?   

使用特权

评论回复
10
touser| | 2021-10-5 09:51 | 只看该作者
能够使用环形buffer吗?      

使用特权

评论回复
11
pklong| | 2021-10-5 09:51 | 只看该作者
接收的时候,需要在中断配置dma吗?   

使用特权

评论回复
12
htmlme| | 2021-10-5 09:52 | 只看该作者
如何知道是否接收到变量呢?      

使用特权

评论回复
13
yujielun| | 2021-10-5 09:52 | 只看该作者
I2C标准库都有配置吗

使用特权

评论回复
14
typeof| | 2021-10-5 09:52 | 只看该作者
需要中断吗?        

使用特权

评论回复
15
usysm| | 2021-10-5 09:53 | 只看该作者
DMA一般都是用在串口上,iic没有试过。   

使用特权

评论回复
16
深圳哥| | 2022-3-31 16:07 | 只看该作者
请问下,可以iic1与iic2,板子上的二个硬件接口对接起来,可以同时跑收发??

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

132

主题

293

帖子

41

粉丝