LNSC-2420-Firmware/src/i2c_dma.c

172 lines
4.2 KiB
C

#include <string.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/dma.h>
#include "i2c_dma.h"
#define DMA_CHANNEL_TX DMA_CHANNEL2
#define DMA_CHANNEL_RX DMA_CHANNEL3
#define TIMEOUT_COUNTER_VALUE 100 // milliseconds
static uint8_t m_wdata[2];
static uint8_t m_wlen;
static uint8_t m_rdata[32];
static uint8_t m_rlen;
static uint8_t m_addr;
static uint32_t m_i2c;
static uint32_t m_timeout_counter;
typedef enum {
IDLE,
SEND_DATA,
RECEIVE_DATA
} i2c_dma_state_t;
static i2c_dma_state_t m_state;
static i2c_dma_callback_t m_callback;
static void setup_txdma(void)
{
dma_channel_reset(DMA1, DMA_CHANNEL_TX);
dma_set_priority(DMA1, DMA_CHANNEL_TX, DMA_CCR_PL_LOW);
dma_set_memory_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_MSIZE_8BIT);
dma_set_peripheral_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_PSIZE_8BIT);
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_TX);
dma_set_read_from_memory(DMA1, DMA_CHANNEL_TX);
dma_set_peripheral_address(DMA1, DMA_CHANNEL_TX, (uint32_t) &I2C_TXDR(m_i2c));
// send m_wlen bytes from m_wdata
dma_set_memory_address(DMA1, DMA_CHANNEL_TX, (uint32_t)m_wdata);
dma_set_number_of_data(DMA1, DMA_CHANNEL_TX, m_wlen);
dma_enable_channel(DMA1, DMA_CHANNEL_TX);
i2c_enable_txdma(m_i2c);
}
static void setup_rxdma(void)
{
dma_channel_reset(DMA1, DMA_CHANNEL_RX);
dma_set_priority(DMA1, DMA_CHANNEL_RX, DMA_CCR_PL_LOW);
dma_set_memory_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_MSIZE_8BIT);
dma_set_peripheral_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_PSIZE_8BIT);
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_RX);
dma_set_read_from_peripheral(DMA1, DMA_CHANNEL_RX);
dma_set_peripheral_address(DMA1, DMA_CHANNEL_RX, (uint32_t) &I2C_RXDR(m_i2c));
// receive m_rlen bytes and store them in m_rdata
dma_set_memory_address(DMA1, DMA_CHANNEL_RX, (uint32_t)m_rdata);
dma_set_number_of_data(DMA1, DMA_CHANNEL_RX, m_rlen);
dma_enable_channel(DMA1, DMA_CHANNEL_RX);
i2c_enable_rxdma(m_i2c);
}
void i2c_dma_init(i2c_dma_callback_t callback)
{
m_callback = callback;
m_state = IDLE;
}
void i2c_dma_loop(void)
{
switch(m_state) {
case IDLE:
// nothing to do
return;
case SEND_DATA:
//if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :(
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_TX, DMA_TCIF)) {
dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_TX, DMA_TCIF);
if(m_rlen) {
m_state = RECEIVE_DATA;
i2c_set_7bit_address(m_i2c, m_addr);
i2c_set_read_transfer_dir(m_i2c);
i2c_set_bytes_to_transfer(m_i2c, m_rlen);
i2c_send_start(m_i2c);
i2c_enable_autoend(m_i2c); // important to do it after start() for a proper repeated start!
setup_rxdma();
} else {
// no RX requested => we are done here
m_state = IDLE;
m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, NULL, 0);
}
}
if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) {
// there should be no NACK during the transfer => abort
I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag
m_state = IDLE;
m_callback(I2C_DMA_EVT_NAK, NULL, 0);
}
break;
case RECEIVE_DATA:
//if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :(
if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_RX, DMA_TCIF)) {
dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_RX, DMA_TCIF);
m_state = IDLE;
m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, m_rdata, m_rlen);
}
if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) {
// there should be no NACK during the transfer => abort
I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag
m_state = IDLE;
m_callback(I2C_DMA_EVT_NAK, NULL, 0);
}
break;
}
m_timeout_counter--;
if(m_timeout_counter == 0) {
m_callback(I2C_DMA_EVT_TIMEOUT, NULL, 0);
}
}
void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_t wn, size_t rn)
{
m_wlen = wn;
m_rlen = rn;
memcpy(m_wdata, w, wn);
m_state = SEND_DATA;
m_i2c = i2c;
m_addr = addr;
i2c_set_7bit_address(i2c, addr);
i2c_set_write_transfer_dir(i2c);
i2c_set_bytes_to_transfer(i2c, wn);
if(rn) {
i2c_disable_autoend(i2c); // prepare repeated start
} else {
i2c_enable_autoend(i2c); // stop condition after write transfer
}
m_timeout_counter = TIMEOUT_COUNTER_VALUE;
i2c_send_start(i2c); // start the transfer. The rest is handled by the DMA.
setup_txdma();
}