IOsetting / py32f0-template

Puya PY32F002A PY32F003 PY32F030 GNU GCC SDK, template and examples
160 stars 60 forks source link

Examples of I2C DMA #41

Open jonnor opened 4 months ago

jonnor commented 4 months ago

Hi, first of all: thank you for this fantastic repository. The build system setup is super pragmatic, easy to work with - and everything I have tried so far works nicely. And there is an impressive amount of examples.

I have one project with an accelerometer, where I would like to read at high rates (from the built-in FIFO). And it is also doing computationally intensive processing (some DSP and machine learning). So I would be very interested in using the DMA support to enable doing work on the CPU while the transfers are ongoing. I see that there is a DMA_CHANNEL_MAP_I2C* in the code. So there does seem to be support for this. There are DMA examples for the ADC for example, but I have not seen anything for I2C+DMA. Does anyone have such examples and/or tips&tricks? It is basically just receive that would benefit from DMA. SPI+DMA would also be relevant

IOsetting commented 4 months ago

I haven't tried I2C DMA, in my work I use timer to trigger I2C reading. Have you tried the I2C DMA examples in Puya's SDK? https://github.com/OpenPuya/PY32F0xx_Firmware/tree/master/Projects/PY32F030-STK/Example_LL/I2C

deividAlfa commented 2 months ago

An example sending a 128 Byte buffer to an I2C device using DMA:

example.c

#define I2C_ADDRESS 0x3C

#define SCL_Port        GPIOF
  #define SCL_Pin       LL_GPIO_PIN_1
  #define SCL_AF        LL_GPIO_AF12_I2C

#define SDA_Port        GPIOA
  #define SDA_Pin       LL_GPIO_PIN_7
  #define SDA_AF        LL_GPIO_AF12_I2C

volatile uint8_t dma_busy;
__attribute__((aligned(4))) uint8_t buffer[128];

const LL_DMA_InitTypeDef DMAInit = {
    .Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH,
    .Mode = LL_DMA_MODE_NORMAL,
    .PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT,
    .MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT,
    .PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE,
    .MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE,
    .Priority = LL_DMA_PRIORITY_HIGH,
};

void i2c_dma_init(void){
  LL_GPIO_Init(SCL_Port, &(LL_GPIO_InitTypeDef){ SCL_Pin, LL_GPIO_MODE_ALTERNATE, LL_GPIO_SPEED_FREQ_VERY_HIGH, LL_GPIO_OUTPUT_OPENDRAIN, LL_GPIO_PULL_NO, SCL_AF });
  LL_GPIO_Init(SDA_Port, &(LL_GPIO_InitTypeDef){ SDA_Pin, LL_GPIO_MODE_ALTERNATE, LL_GPIO_SPEED_FREQ_VERY_HIGH, LL_GPIO_OUTPUT_OPENDRAIN, LL_GPIO_PULL_NO, SDA_AF });
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
  LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
  LL_SYSCFG_SetDMARemap_CH1(LL_SYSCFG_DMA_MAP_I2C_TX);
  LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, (LL_DMA_InitTypeDef*)&DMAInit);
  LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_1, LL_I2C_DMA_GetRegAddr(I2C1));
  LL_I2C_DeInit(I2C1);
  LL_I2C_Init(I2C1, &(LL_I2C_InitTypeDef){ 400000, LL_I2C_DUTYCYCLE_2, 0, I2C_CR1_ACK }); //LL_I2C_DUTYCYCLE_16_9
  LL_I2C_DisableBitPOS(I2C1);
  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
  NVIC_SetPriority(DMA1_Channel1_IRQn, 1);
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

void send_i2c_dma(void){
  while(dma_busy){ ; }
  LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uint32_t)buffer);
  LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, sizeof(buffer));
  LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

  LL_I2C_GenerateStartCondition(I2C1);              // Start condition
  while(!LL_I2C_IsActiveFlag_SB(I2C1));            

  LL_I2C_TransmitData8(I2C1, I2C_ADDRESS);          // Device address
  while(!LL_I2C_IsActiveFlag_ADDR(I2C1));
  LL_I2C_ClearFlag_ADDR(I2C1);

  LL_I2C_TransmitData8(I2C1, modeData);             // Device memory register address (If applicable)
  while(!LL_I2C_IsActiveFlag_TXE(I2C1));

  dma_busy = 1;                                     // Set busy flag
  LL_I2C_EnableDMAReq_TX(I2C1);                     // Enable DMA request, transfer starts inmediately
}

py32f0xx_it.c

void DMA1_Channel1_IRQHandler(void){
  extern volatile uint8_t dma_busy;
  LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
  LL_DMA_ClearFlag_GI1(DMA1);
  dma_busy = 0;
}