Forum: Mikrocontroller und Digitale Elektronik M4 (samd51) läuft nicht als i2c master


von i2c_problem (Gast)


Lesenswert?

Hallo Zusammen,

jemand eine Idee warum aus meinen Portpins PB08 und PB09 absolut kein 
Signal kommt?
Ich habe zum ersten mal versucht den i2c am atsamD51j18 aufzusetzen, und 
bisher scheitert es komplett. Es gab noch keinen einzigen Zucker an den 
beiden Portpins... Für meinen Geschmack müsste er wenigstens mal 
irgendwie zucken wenn z.B. das erste mal ins ADDR-Register geschrieben 
wird.
Die Konfiguration is grob aus Datenblatt und AN(AT11628 für samd21) 
zusammengeführt.

Hardwaretechnisch hängt an der anderen Seite über Pullups (4k4) nur ein 
DAC80501 im i2c-Betriebsmodus.

Wäre über hinweise sehr dankbar!




1
/*
2
 * Prototyp_1.c
3
 *
4
 * Created: 30.01.2020 11:07:41
5
 * Author : i2c
6
 */ 
7
8
9
#include "sam.h"
10
#include <stdbool.h>
11
12
13
14
#define CMD_DAC_DEVID 0b00000001
15
#define SLAVE_ADDR_DAC 0x72
16
#define BUF_SIZE 3
17
18
19
20
21
uint8_t tx_buf[BUF_SIZE]={1,2,3};
22
uint8_t rx_buf[BUF_SIZE];
23
uint8_t i;
24
volatile bool tx_done=false, rx_done=false;
25
26
27
28
29
30
void clk_init(void)
31
{
32
  
33
  // Clock von GCLK0 ausgeben auf PB14
34
  PORT->Group[1].PMUX[7].bit.PMUXE = 12; // Multiplex M
35
  PORT->Group[1].DIRSET.reg = PORT_PB14; // 48MHz ausgeben auf Portpin
36
  PORT->Group[1].PINCFG[14].reg = PORT_PINCFG_PMUXEN;
37
  GCLK->GENCTRL[0].reg =  GCLK_GENCTRL_OE |
38
              GCLK_GENCTRL_GENEN;
39
  
40
}
41
42
43
44
45
46
void i2c_init(void)
47
{
48
    
49
  //SERCOM4 Core mit Generic Clock Generator 0 verbinden und aktivieren
50
  GCLK->PCHCTRL[34].reg =  GCLK_PCHCTRL_CHEN |
51
              GCLK_PCHCTRL_GEN_GCLK0;
52
              
53
  MCLK->APBDMASK.bit.SERCOM4_ = 1;    // Main-Clock auf SERCOM4 aktivieren
54
  
55
  
56
  SERCOM4->I2CM.CTRLA.bit.SWRST = 1;    // Softwarereset für SERCOM4
57
  while(SERCOM4->I2CM.CTRLA.bit.SWRST | SERCOM4->I2CM.SYNCBUSY.bit.SWRST);  // warten bis reset fertig
58
  
59
  SERCOM4->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE(5) | // muss Wert 5 haben in Master-Config
60
                SERCOM_I2CM_CTRLA_INACTOUT(1); // 55US Inactive Time-Out
61
62
  SERCOM4->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
63
  while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
64
  
65
  //SERCOM4->I2CM.CTRLC.reg = SERCOM_I2CM_CTRLC_DATA32B; // Dataregister mit 32 Bit beschreiben
66
  
67
  SERCOM4->I2CM.BAUD.reg = 59;  // Baudrate (400kb/s) 
68
  while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
69
  
70
  SERCOM4->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB; // Interrupt
71
  
72
  SERCOM4->I2CM.STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1); 
73
  while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
74
  
75
  SERCOM4->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_ENABLE;    // Aktivieren
76
  while(SERCOM4->I2CM.SYNCBUSY.bit.ENABLE);
77
  
78
  
79
  PORT->Group[1].PMUX[4].bit.PMUXO = 3;
80
  PORT->Group[1].PMUX[4].bit.PMUXE = 3;
81
    
82
  PORT->Group[1].PINCFG[8].reg = PORT_PINCFG_PMUXEN;
83
  PORT->Group[1].PINCFG[9].reg = PORT_PINCFG_PMUXEN;
84
85
  
86
  NVIC_DisableIRQ(SERCOM4_2_IRQn);
87
  NVIC_ClearPendingIRQ(SERCOM4_2_IRQn);
88
  NVIC_SetPriority(SERCOM4_2_IRQn, 0);
89
  NVIC_EnableIRQ(SERCOM4_2_IRQn);
90
  
91
  
92
  
93
}
94
95
96
97
98
99
100
101
void SERCOM4_Handler(void)
102
{
103
  
104
  /* Master on bus interrupt checking */
105
  if(SERCOM4->I2CM.INTFLAG.bit.MB)
106
  {
107
    if(i==BUF_SIZE)
108
    {
109
      /* After transferring the last byte stop condition will be sent */
110
      SERCOM4->I2CM.CTRLB.bit.CMD = 0x3;
111
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
112
      tx_done=true;
113
      i=0;
114
    }else
115
    {
116
      /* placing the data from transmitting buffer to DATA register*/
117
      SERCOM4->I2CM.DATA.reg = tx_buf[i++];
118
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
119
    }
120
  }
121
  
122
  /* Slave on bus interrupt checking */
123
  if(SERCOM4->I2CM.INTFLAG.bit.SB)
124
  {
125
    if(i==(BUF_SIZE-1))
126
    {
127
      /* NACK should be sent before reading the last byte */
128
      SERCOM4->I2CM.CTRLB.reg|=SERCOM_I2CM_CTRLB_ACKACT;
129
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
130
      
131
      SERCOM4->I2CM.CTRLB.bit.CMD=0x3;
132
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
133
      
134
      rx_buf[i++]=SERCOM4->I2CM.DATA.reg;
135
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);rx_done=true;
136
    }else
137
    {
138
      SERCOM4->I2CM.CTRLB.reg&=~SERCOM_I2CM_CTRLB_ACKACT;
139
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
140
      
141
      rx_buf[i++]=SERCOM4->I2CM.DATA.reg;
142
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
143
      
144
      /* sending ACK after reading each byte */
145
      SERCOM4->I2CM.CTRLB.bit.CMD=0x2;
146
      while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
147
    }
148
  }
149
  
150
  
151
}
152
153
154
155
156
void i2c_master_transact(void)
157
{
158
  i=0;
159
  
160
  /* Acknowledge section is set as ACKsignal by writing 0 in ACKACT bit */
161
  SERCOM4->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
162
  while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
163
  
164
  /* slave address with Write(0) */
165
  SERCOM4->I2CM.ADDR.reg = (SLAVE_ADDR_DAC<<1) | 0;
166
  while(!tx_done);
167
  
168
  i=0;
169
  
170
  /* Acknowledge section is set as ACK signal by writing 0 in ACKACT bit */
171
  SERCOM4->I2CM.CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
172
  while(SERCOM4->I2CM.SYNCBUSY.bit.SYSOP);
173
  
174
  /* slave address with read (1) */
175
  SERCOM4->I2CM.ADDR.reg = (SLAVE_ADDR_DAC<<1) | 1;
176
  while(!rx_done);
177
  
178
  /*interrupts are cleared */
179
  SERCOM4->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB;
180
  
181
}
182
183
184
185
186
187
188
void sleep(uint32_t count)
189
{
190
  for(;count>0;count--)
191
  {
192
    asm volatile("NOP");
193
  }
194
}
195
196
197
198
199
200
201
202
203
204
205
206
int main(void)
207
{
208
  
209
  __enable_irq();
210
  
211
  clk_init();
212
  
213
214
  i2c_init();
215
  
216
  
217
  
218
219
    while (1) 
220
    {
221
    
222
    i2c_master_transact();
223
224
    
225
        
226
    sleep(500);
227
        
228
    
229
    
230
    }
231
}

von Peter D. (peda)


Lesenswert?

Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Und nicht kiloweise unnötig Leerzeilen lassen.

von Kondensator (Gast)


Lesenswert?

Pullupwiderstand drin?

von Stephan (Gast)


Lesenswert?

Sicher, dass es nicht P_A_09 und 08 sind?

und#1:
ich vermissse so etwas in der Art:

void i2c_pin_init(){
  pin_set_peripheral_function(PINMUX_PA08D_SERCOM2_PAD0);
  pin_set_peripheral_function(PINMUX_PA09D_SERCOM2_PAD1);
}

und#2:
PullUp 4k7 ist meiner unmassgeblichen Erfahrung nach VIEL zu hoch...
Ich nehme in 3V3 Systemen 1k .. 2k2 (je nach Leitungslänge, I2C-Speed 
und potenziellen Störern); Aber das wird natürlich erst relevant, WENN 
aus den (richtigen) Ports etwas herauskommt...

VG, Stephan

von Rudolph (Gast)


Lesenswert?

Also PB08 und PB09 passen zumindest zu SERCOM4.

Vielleicht mal so konfigurieren:

REG_PORT_WRCONFIG1 =
 PORT_WRCONFIG_HWSEL |
 PORT_WRCONFIG_WRPINCFG |
 PORT_WRCONFIG_WRPMUX |
 PORT_WRCONFIG_PMUX(3) | /* SERCOM4 */
 PORT_WRCONFIG_DRVSTR |
 PORT_WRCONFIG_PINMASK(0x0003) | /* PB08 + PB09 */
 PORT_WRCONFIG_PMUXEN;

von i2c_problem (Gast)


Lesenswert?

@Rudolph
hat leider nichts geändert.


@Stephan
1
  PORT->Group[1].PMUX[4].bit.PMUXO = 3;
2
  PORT->Group[1].PMUX[4].bit.PMUXE = 3;
3
    
4
  PORT->Group[1].PINCFG[8].reg = PORT_PINCFG_PMUXEN;
5
  PORT->Group[1].PINCFG[9].reg = PORT_PINCFG_PMUXEN;

Das sollte die Pins entsprechend schalten. Und ja für SERCOM4 wollte ich 
PB08 und PB09.






*@all*
Ich habe jetzt auch schon diverse andere Pins und SERCOM-Schnittstellen 
versucht, leider ohne Erfolg. Beim duchgehen im Debugger (Atmelstudio) 
erkennt man auch diverse Änderungen der Register während der 
Initialisierung. Wenn ich dann aber in i2c_master_transact() in das 
ADDR-Register schreibe ändert sich im Register nichts(oder es wird 
zumindest nicht angezeigt).

Scheint wohl eher etwas grundlegendes zu sein... noch jemand Ideen oder 
vllt eine funktionsfähige Version?

von Stephan (Gast)


Lesenswert?

Ich würde bei der Fehlersuche trotzdem ganz unten anfangen...
Also feststellen, ob der SCK Pin überhaupt gesetzt gelöscht werden kann, 
gleiches für SDA... wenn da so garnix rauskommt tippe ich auf ein port 
initialisierungsproblem. Ich kenne aber den D51/SERCON nicht; benutze 
selbst den SAM4S, da ist an der Stelle ein bisschen traditioneller Stoff 
gefragt.

von Peter D. (peda)


Lesenswert?

Geh dochmal systematisch vor.
Prüfe, ob die Pins überhaupt ansprechbar sind und dann probiere ein 
SW-I2C.
Wenn Du eh im Main wartest, ist HW-I2C auch nicht schneller, nur viel 
komplizierter und störempfindlicher.

von i2c_problem (Gast)


Lesenswert?

1
SERCOM4->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_ENABLE;    // Aktivieren


hier fehlt ein "|" vor dem "=" .... damit wird der "MODE" überschrieben 
und es nicht als Master initialisiert....grrrrrrrrrrrrrr
Besten Dank für die Hinweise/Hilfe

von Rudolph (Gast)


Lesenswert?

i2c_problem schrieb:
> @Rudolph
> hat leider nichts geändert.

Das war im Detail auch Unsinn, irgendwie hatte ich spontan die Idee, 
dass das WRCONFIG Register nur 8 Pins konfiguriert.

PORT->Group[1].WRCONFIG.reg =
  PORT_WRCONFIG_WRPINCFG |
  PORT_WRCONFIG_WRPMUX |
  PORT_WRCONFIG_PMUX(3) |    /* SERCOM4 */
  PORT_WRCONFIG_DRVSTR |
  PORT_WRCONFIG_PINMASK(0x0300) | /* PB08 + PB09 */
  PORT_WRCONFIG_PMUXEN;


Weil das sowieso schon länger sinnlos herum liegt habe ich gerade mal 
den I2C auf einem SAMC21 grundsätzlich zum Laufen gebracht.
Die Peripherie ist mit dem SAMD51/SAME51 ja weitgehend identisch, der 
SAMD51 hat nur von allem noch viel mehr.
1
#include "sam.h"
2
3
volatile uint8_t system_tick = 0;
4
5
void SysTick_Handler(void)
6
{
7
  system_tick = 42;
8
}
9
10
11
void init_clock(void)
12
{
13
  REG_NVMCTRL_CTRLB = NVMCTRL_CTRLB_RWS(2);  // Set the NVM Read Wait States to 2, Since the operating frequency will be 48 MHz
14
15
  REG_OSCCTRL_XOSCCTRL =  OSCCTRL_XOSCCTRL_STARTUP(6) |    // 1,953 ms
16
              OSCCTRL_XOSCCTRL_RUNSTDBY |
17
              OSCCTRL_XOSCCTRL_AMPGC |
18
              OSCCTRL_XOSCCTRL_GAIN(3) |
19
              OSCCTRL_XOSCCTRL_XTALEN |
20
              OSCCTRL_XOSCCTRL_ENABLE;
21
22
  while((REG_OSCCTRL_STATUS & OSCCTRL_STATUS_XOSCRDY) == 0);
23
24
  /* configure the PLL, source = XOSC, pre-scaler = 8, multiply by 24 -> 48MHz clock from 16MHz input */
25
  REG_OSCCTRL_DPLLCTRLB = OSCCTRL_DPLLCTRLB_DIV(3) | OSCCTRL_DPLLCTRLB_REFCLK(1); // setup PLL to use XOSC input
26
  REG_OSCCTRL_DPLLRATIO = OSCCTRL_DPLLRATIO_LDRFRAC(0x0) | OSCCTRL_DPLLRATIO_LDR(23);
27
  REG_OSCCTRL_DPLLCTRLA = OSCCTRL_DPLLCTRLA_RUNSTDBY | OSCCTRL_DPLLCTRLA_ENABLE;
28
  while((REG_OSCCTRL_DPLLSTATUS & OSCCTRL_DPLLSTATUS_CLKRDY) != 0); // wait for the pll to be ready
29
30
  REG_GCLK_GENCTRL0 = GCLK_GENCTRL_DIV(0) |
31
            GCLK_GENCTRL_RUNSTDBY |
32
            GCLK_GENCTRL_GENEN |
33
            //GCLK_GENCTRL_SRC_XOSC |
34
            GCLK_GENCTRL_SRC_DPLL96M |
35
            GCLK_GENCTRL_IDC ;
36
37
  while((REG_GCLK_SYNCBUSY & GCLK_SYNCBUSY_GENCTRL0) != 0);  // wait for the synchronization between clock domains is complete
38
}
39
40
41
void init_io(void)
42
{
43
  REG_PORT_DIRSET0 = PORT_PA27; /* Debug-LED */
44
  REG_PORT_DIRSET0 = PORT_PA22; /* CAN_EN_1 */
45
  REG_PORT_DIRSET0 = PORT_PA23; /* CAN_STB_1 */
46
47
  REG_PORT_OUTSET0 = PORT_PA22; /* CAN_EN_1 */
48
  REG_PORT_OUTSET0 = PORT_PA23; /* CAN_STB_1 */
49
}
50
51
52
void init_i2c(void)
53
{
54
  /* configure SERCOM1 SCL PA17 and SERCOM1 SDA on PA16 */
55
  PORT->Group[0].WRCONFIG.reg =
56
    PORT_WRCONFIG_HWSEL |
57
    PORT_WRCONFIG_WRPINCFG |
58
    PORT_WRCONFIG_WRPMUX |
59
    PORT_WRCONFIG_PMUX(2) |    /* SERCOM1 */
60
    PORT_WRCONFIG_DRVSTR |
61
    PORT_WRCONFIG_PINMASK(0x0003) | /* PA16 + PA17 */
62
    PORT_WRCONFIG_PMUXEN;
63
64
  REG_MCLK_APBCMASK |= MCLK_APBCMASK_SERCOM1;
65
  GCLK->PCHCTRL[20].reg = GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN; /* setup SERCOM1 to use GLCK0 -> 48MHz */
66
  SERCOM1->I2CM.CTRLA.reg = 0x00; /* disable SERCOM1 -> enable config */
67
  SERCOM1->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE(5); /* I2C Master */
68
  SERCOM1->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD(239); /* 48 / (2 * (baudval + 1)) -> @48Mhz: 239 = 100kHz */
69
  SERCOM1->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
70
  SERCOM1->I2CM.CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE; /* activate SERCOM1 */
71
  while(SERCOM1->I2CM.SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_ENABLE); /* wait for SERCOM1 to be ready */
72
}
73
74
75
int main(void)
76
{
77
  uint8_t led_delay = 0;
78
  uint8_t i2c_delay = 0;
79
  
80
  init_clock();
81
  init_io();
82
  init_i2c();
83
84
  SysTick_Config(48000000 / 200); /* configure and enable Systick for 5ms ticks */
85
86
  while (1)
87
  {
88
    if(system_tick)
89
    {
90
      system_tick = 0;
91
      
92
      led_delay++;
93
      if(led_delay > 39)
94
      {
95
        led_delay = 0;
96
        REG_PORT_OUTTGL0 = PORT_PA27;
97
      }
98
99
      i2c_delay++;
100
      if(i2c_delay > 49)
101
      {
102
        i2c_delay = 0;
103
        SERCOM1->I2CM.ADDR.reg = 23;
104
      }
105
    }
106
  }
107
}

Das ist jetzt funktional nur ein Test, damit zappeln meine I2C Pins aber 
wenigstens.

von Rudolph (Gast)


Lesenswert?

Damn, zu langsam, aber dafür bin ich auch einen Schritt weiter in 
Richtung I2C - falls das doch irgendwann mal benötigt werden sollte...). 
:-)

von i2c_problem (Gast)


Lesenswert?

@Rudolph

ja, minimal zu langsam. Trotzdem vielen Dank für die Mühe!

von Peter D. (peda)


Lesenswert?

i2c_problem schrieb:
> SERCOM4->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_ENABLE;    // Aktivieren
>
> hier fehlt ein "|" vor dem "=" .... damit wird der "MODE" überschrieben
> und es nicht als Master initialisiert....grrrrrrrrrrrrrr

Solche mehrfachen Zugriffe auf das selbe Register sind eigentlich immer 
potentielle Fallgruben.
Ich setze daher immmer alle nötigen Bits auf einmal. Dann brauche ich 
mir nicht zu merken, was mal irgendwann früher gesetzt wurde.
Diese |= und &= Orgien mag ich nicht.

: Bearbeitet durch User
von Rudolph R. (rudolph)


Lesenswert?

Peter D. schrieb:
> Solche mehrfachen Zugriffe auf das selbe Register sind eigentlich immer
> potentielle Fallgruben.
> Ich setze daher immmer alle nötigen Bits auf einmal. Dann brauche ich
> mir nicht zu merken, was mal irgendwann früher gesetzt wurde.
> Diese |= und &= Orgien mag ich nicht.

Eleganter wäre das so:

SERCOM1->I2CM.CTRLA.bit.ENABLE = 1; /* activate SERCOM1 */

Der Punkt ist halt, das setzen des ENABLE Bits und das kurze Warten 
darauf, dass das passiert ist, macht schon Sinn so am Ende der 
Initialisierung.
Denn im Gegensatz zu den AVR werden bei den ATSAM die meisten Bits in 
den meisten Registern gesperrt, sobald die Peripherie-Einheit aktiviert 
ist.
Also mindestens bei den ATSAMD2x, ATSAMC2x und ATSAMD/ATSAME5x ist das 
so.

Man kann zum Beispiel nicht einfach so den SPI Takt ändern,
man muss das ENABLE der SERCOM Einheit löschen, auf SYNCBUSY_ENABLE 
warten, kann dann das BAUD Register neu schreiben, ENABLE wieder setzen 
und noch mal auf SYNCBUSY_ENABLE warten.

Ausgesuchte Bits wie RXEN beim SPI sind davon ausgenommen.
Andere wie CPOL und CPHA sind aber gesperrt, was schon mal lästig sein 
kann.

Wenn man da lustig alles auf einmal setzt muss das nicht unbedingt den 
gewünschten Effekt haben.

Und das ist auch nur Level 1 der Absicherung.
Level 2 wäre den PAC - Peripheral Access Controller - anzuwerfen und 
noch mehr zu sperren, dann bekommt man sogar einen Access Violation 
Interrupt wenn doch ein Schreib-Zugriff passiert.

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.