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


von Benajmin Buxbaum (Gast)


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:
1
I2C_HandleTypeDef hi2c1;
2
DMA_HandleTypeDef hdma_i2c1_rx;
3
DMA_HandleTypeDef hdma_i2c1_tx;
4
5
static void MX_GPIO_Init(void);
6
static void MX_DMA_Init(void);
7
static void MX_I2C1_Init(void);
8
9
int main(void)
10
{
11
12
    HAL_Init();
13
14
    SystemClock_Config();
15
16
    MX_GPIO_Init();
17
    MX_DMA_Init();
18
    MX_I2C1_Init();
19
20
    // Enable MPU6050 and use Gyro PLL as clock source
21
    uint8_t buf = MPU6050_CLOCK_PLL_XGYRO;
22
    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) {
23
        Error_Handler();
24
    }
25
26
    // (Hier kommt noch mehr Code...)
27
}
28
29
/* I2C1 init function */
30
static void MX_I2C1_Init(void)
31
{
32
    hi2c1.Instance = I2C1;
33
    hi2c1.Init.ClockSpeed = 100000;
34
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
35
    hi2c1.Init.OwnAddress1 = 0;
36
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
37
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
38
    hi2c1.Init.OwnAddress2 = 0;
39
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
40
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
41
42
    /* Peripheral clock enable */
43
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
44
    {
45
        Error_Handler();
46
    }
47
48
}
49
50
static void MX_DMA_Init(void) 
51
{
52
    __HAL_RCC_DMA1_CLK_ENABLE();
53
54
    /* DMA interrupt init */
55
    /* DMA1_Channel6_IRQn interrupt configuration */
56
    HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
57
    HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
58
59
    /* DMA1_Channel7_IRQn interrupt configuration */
60
    HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
61
    HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
62
}
63
64
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
65
{
66
67
  GPIO_InitTypeDef GPIO_InitStruct;
68
  if(hi2c->Instance==I2C1)
69
  {  
70
    /**I2C1 GPIO Configuration    
71
    PB6     ------> I2C1_SCL
72
    PB7     ------> I2C1_SDA 
73
    */
74
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
75
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
76
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
77
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
78
79
    /* Peripheral clock enable */
80
81
    __HAL_RCC_I2C1_CLK_ENABLE();
82
    hdma_i2c1_rx.Instance = DMA1_Channel7;
83
    hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
84
    hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
85
    hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE;
86
    hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
87
    hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
88
    hdma_i2c1_rx.Init.Mode = DMA_NORMAL;
89
    hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW;
90
91
    if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK) {
92
        Error_Handler();
93
    }
94
95
    __HAL_LINKDMA(hi2c,hdmarx,hdma_i2c1_rx);
96
97
    hdma_i2c1_tx.Instance = DMA1_Channel6;
98
    hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
99
    hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
100
    hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE;
101
    hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
102
    hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
103
    hdma_i2c1_tx.Init.Mode = DMA_NORMAL;
104
    hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_LOW;
105
106
    if (HAL_DMA_Init(&hdma_i2c1_tx) != HAL_OK) {
107
        Error_Handler();
108
    }
109
110
    __HAL_LINKDMA(hi2c,hdmatx,hdma_i2c1_tx);
111
112
  }
113
114
}

...sieht irgendjemand, was dabei schief geht?

von hp-freund (Gast)


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

von Benajmin Buxbaum (Gast)


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.

von seho85 (Gast)


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
1
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

von aSma>> (Gast)


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.

von Benajmin Buxbaum (Gast)


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:
1
int main(void)
2
{
3
4
    HAL_Init();
5
6
    SystemClock_Config();
7
8
    MX_GPIO_Init();
9
    MX_DMA_Init();
10
    MX_I2C1_Init();
11
    MX_USART1_UART_Init();
12
13
    // Enable MPU6050 and use Gyro PLL as clock source
14
    uint8_t buf = MPU6050_CLOCK_PLL_XGYRO;
15
    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) {
16
        Error_Handler();
17
    }
18
19
    while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
20
21
    while (1)
22
    {
23
        HAL_Delay(2000);
24
        HAL_UART_Transmit(&huart1, "Endless Loop\r\n", strlen("Endless Loop\r\n"), 5000);
25
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
26
    }
27
28
}

von seho85 (Gast)


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

von Benajmin Buxbaum (Gast)


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?

von Benajmin Buxbaum (Gast)


Lesenswert?

Habs eben interessehalber mal getestet, macht aber leider auch keinen 
Unterschied.

von seho85 (Gast)


Angehängte Dateien:

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

von Vincent H. (vinci)


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.

von Benajmin Buxbaum (Gast)


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!

von Benajmin Buxbaum (Gast)


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.