www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik STM32: I2C mit DMA und HAL


Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tue mich gerade etwas schwer damit, I2C auf einem STM32F103C8T6 (blaues 
Chinaboard) zum Laufen zu bekommen. Mit Polling klappte es bereits 
wunderbar, so bald ich die DMA einschalte, springt der Chip nach ca. 10s 
in den Error Handler. Ausgelesen werden soll ein MPU6050.

Der Initcode stammt komplett aus CubeMX. Habe die nicht relevanten 
Funktionen rausgeschmisse, damit es übersichtlicher wird:
I2C_HandleTypeDef hi2c1;
DMA_HandleTypeDef hdma_i2c1_rx;
DMA_HandleTypeDef hdma_i2c1_tx;

static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_I2C1_Init(void);

int main(void)
{

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();
    MX_DMA_Init();
    MX_I2C1_Init();

    // Enable MPU6050 and use Gyro PLL as clock source
    uint8_t buf = MPU6050_CLOCK_PLL_XGYRO;
    if (HAL_I2C_Mem_Write_DMA(&hi2c1, MPU6050_ADDRESS_AD0_LOW << 1, MPU6050_RA_PWR_MGMT_1, I2C_MEMADD_SIZE_8BIT, &buf, 1) != HAL_OK) {
        Error_Handler();
    }

    // (Hier kommt noch mehr Code...)
}

/* I2C1 init function */
static void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;

    /* Peripheral clock enable */
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        Error_Handler();
    }

}

static void MX_DMA_Init(void) 
{
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA1_Channel6_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);

    /* DMA1_Channel7_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
}

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(hi2c->Instance==I2C1)
  {  
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Peripheral clock enable */

    __HAL_RCC_I2C1_CLK_ENABLE();
    hdma_i2c1_rx.Instance = DMA1_Channel7;
    hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_i2c1_rx.Init.Mode = DMA_NORMAL;
    hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW;

    if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK) {
        Error_Handler();
    }

    __HAL_LINKDMA(hi2c,hdmarx,hdma_i2c1_rx);

    hdma_i2c1_tx.Instance = DMA1_Channel6;
    hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_i2c1_tx.Init.Mode = DMA_NORMAL;
    hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_LOW;

    if (HAL_DMA_Init(&hdma_i2c1_tx) != HAL_OK) {
        Error_Handler();
    }

    __HAL_LINKDMA(hi2c,hdmatx,hdma_i2c1_tx);

  }

}

...sieht irgendjemand, was dabei schief geht?

Autor: hp-freund (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was ich nicht sehe sind Sende- und Empfangspuffer und der richtige 
Aufruf der HAL Funtionen.

Schau dir das Beispiel aus der Lib an.

I2C_TwoBoards_ComDMA

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke für die wiederholte Hilfe :)
Sorry, falls ich etwas begriffsstutzig bin:

Den Buffer übergebe ich doch als Parameter im Funktionsaufrauf in der 
main(), oder? Der Compiler beschwert sich jedenfalls nicht über den 
Aufruf... Unmittelbar vorher wird er als uint_8t deklariert.

Habe versucht, mit so gut wie möglich an dem Beispiel zu orientieren, 
bis auf den Unterschied, dass ich Mem_Transmit, statt Master_Transmit im 
Beispiel, machen muss.

Autor: seho85 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

leider reicht der gepostete Quelltext nicht aus um das Problem 
festzumachen.

Ich vermute du wartest nicht darauf wartest, das der DMA Transfer auch 
wirklich abgeschlossen ist.

In dem vom hp-freund beschrieben Beispiel wird das per
while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY) {  }

bewerkstelligt.

Falls meine Vermutung vollkommen daneben liegt, dann zeig doch bitte mal 
den Code wo das "Warten auf Fertigstellung des DMA Transfers" realisiert 
hast.

MfG
Sebastian

Autor: aSma>> (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die DMA Interrupt Prioritäten sind beide gleich. Ich würde den RX immer 
eine höhere geben. Vielleicht mag der uC das nicht.

Sonst poste deinen Code hier im Anhang.

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Sebastian, du hast recht, das hatte ich nicht drin... habe zum Testen 
allerdings alles weitere so weit auskommentiert, dass nur noch der eine 
Schreibbefehl drin ist, und in der while-Schleife etwas ueber UART 
geschrieben wird, um sehen zu koennen, obs durchlaeuft.

Habe die Wartefunktion ergaenzt, allerdings ohne Erfolg.
Hier das aktuelle Minimalbeispiel:
int main(void)
{

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();
    MX_DMA_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();

    // Enable MPU6050 and use Gyro PLL as clock source
    uint8_t buf = MPU6050_CLOCK_PLL_XGYRO;
    if (HAL_I2C_Mem_Write_DMA(&hi2c1, MPU6050_ADDRESS_AD0_LOW << 1, MPU6050_RA_PWR_MGMT_1, I2C_MEMADD_SIZE_8BIT, &buf, 1) != HAL_OK) {
        Error_Handler();
    }

    while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);

    while (1)
    {
        HAL_Delay(2000);
        HAL_UART_Transmit(&huart1, "Endless Loop\r\n", strlen("Endless Loop\r\n"), 5000);
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
    }

}

Autor: seho85 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

hab es gerade mal mit 'nem MCP23017 durchgespielt.

Hast du innerhalb CubeMX den "I2Cx global Interrupt..." unter "NVIC 
Settings" für die entsprechende I2C Schnittstelle aktiviert?

Der muss aktiviert sein damit der Zugriff auch per DMA funktioniert.

Gruß,
Sebastian

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habe eben mal reingeguckt. "Global Interrupt" sehe ich da nur fuer die 
DMA. Die sind aktiviert und ließen sich auch gar nicht deaktiveren.

Für I2C gibt es nur "Error Interrupt" und "Event Interrupt". Sollte ich 
die mal aktivieren?

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habs eben interessehalber mal getestet, macht aber leider auch keinen 
Unterschied.

Autor: seho85 (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

hmm, komisch.

Bei mir funktionierte es nach dem ich alle Interrupts für das 
entsprechende I2C Interface aktiviert hatte. (Siehe Anhang)

Hab mal den Quelltext der main.c meines Versuches (STM32F3 Discovery + 
MCP23017) auf http://pastebin.com/7GszET42 bereitgestellt.

Gruß,
Sebastian

Autor: Vincent Hamp (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Funktion "HAL_I2C_Mem_Write_DMA" schaltet den DMA Interrupt ein. 
Hast du eine Interrupt Funktion angelegt...?

Wenn nicht, dann springt der Interrupt wohl dorthin wo auch immer ST in 
ihrem Startup-Code definiert hat, dass er hinspringen soll. Das kann 
dann auch gern mal ein "Error Handler" sein.

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich bin dem Problem glaube ich inzwischen auf der Spur, und wie es 
aussieht hat es nichts mit meinen mittelklassigen Programmierkünsten zu 
tun.

Beim Zurückgehen auf die alte Version, die ohne Interrupt lief, hatte 
ich auf einmal die selben merkwürdigen Aussetzer, sporadisch 
funktioniert es dann aber doch immer wieder. Gut möglich also, das 
irgendwo auf der Strecke zwischen Sensor und Board ein Wackelkontakt 
ist, der für die Misere verantwortlich ist.

Ich baue das Ganze am Wochenede nochmal ordentlich auf einen Breadboard 
auf, und berichte nochmal, ob das was gebracht hat!

Autor: Benajmin Buxbaum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Für all jene, die auf der Suche nach Lösungen irgendwann über diesen 
Thread stolpern:

Hatte nichts mit Kabeln oder dem Programm zu tun, sondern der Sensor war 
defekt. Habe testweise ein neues GY-521 Board besorgt, mit dem 
funktioniert alles wunderbar.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.