Обсуждаем контроллеры компании Atmel.
Ответить

Atmega8. SPI не всегда верно работает

Пн апр 22, 2024 20:17:42

Есть два МК atmega8. Работаю на заводской частоте 1МГц.
Хочу передавать задавать в master значение переменной counter от 0 до 3 и передавать её по SPI в slave и чтобы slave через I2C отображал её значение на OLED дисплее SSD1306. Для этого соединил MOSI, MISO, SCK, SS между собой между master и slave. Код писал в двух вариантах: первый работает, а второй почему то нет.
Первый вариант: Подключил к master три кнопки к портам PORTC.1, PORTC.2, PORTC.3, при нажатии на которые переменная counter будет принимать значение 1,2 и 3 соответственно. И всё нормально работает: slave весь OLED-дисплей заполняет этими цифрами как надо.
Вот первый вариант кода для master:
Код:
void SPI_master_settings(void) //настройки  SPI_Master
{
   DDRB|=(1<<MOSI)|(1<<SCK)|(1<<SS);
   PORTB|=(1<<SS);
   DDRB&=~(1<<MISO);
   PORTB|=(1<<MOSI)|(1<<SCK)|(1<<SS);
   SPCR=0;
   SPSR=0;
   SPDR=0;
   SPCR|=(1<<SPIE)| (1<<SPE)| (1<<MSTR) ;
   SPCR|=(1<<SPE);//разрешаем работу SPI
   SPCR|=(1<<MSTR); // МК работает как master
   SPSR&=~(1<<SPI2X); //без удвоения частоты
   SPCR&=~(1<<SPR1);//нам нужен предделитель частоты МК clk/16:
   SPCR|=(1<<SPR0);
   SPCR|=(1<<CPOL) |(1<<CPHA); // импульс отрицательной полярности, задний фронт
   SPCR&=~(1<<DORD); //сперва передаются старшие биты
}
int main()
{
        SPI_master_settings();
   DDRC&=~((1<<3)|(1<<2)|(1<<1));//кнопки
   PORTC|=(1<<3)|(1<<2)|(1<<1);

        if (~PINC&(1<<1)) //задаем значение 1
   {
      SPDR = 1;
      PORTB &= ~(1<<SS);
      while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
      PORTB |=(1<<PB2); //Установить "1" на линии SS
   }
      
   if (~PINC&(1<<2))  //задаем значение 2
   {
      SPDR = 2;
      PORTB &= ~(1<<SS);
      while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
      PORTB |=(1<<PB2); //Установить "1" на линии SS
   }
      
   if (~PINC&(1<<3)) //задаем значение 3
   {
      SPDR = 3;
      PORTB &= ~(1<<SS);
      while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
      PORTB |=(1<<PB2); //Установить "1" на линии SS
   }
}
 


Вот настройки SPI и код main для slave, одинаковый для обоих вариантов (настройки I2C для дисплея не пишу, т.к. там очень много):
Код:
void SPI_slave_settings(void) //настройки  SPI_SLAVE
{
   DDRB&=~((1<<MOSI)|(1<<SCK));
   DDRB|=(1<<MISO);
   PORTB|=(1<<MISO);
   SPCR=0;
   SPSR=0;
   SPDR=0;
   SPCR|=(1<<SPIE);
   SPCR|=(1<<SPE);
   SPCR&=~(1<<MSTR); //МК работает как slave   
   SPCR|=(1<<CPOL);
   SPCR|=(1<<CPHA);
   SPCR&=~(1<<DORD);
   DDRB&=~(1<<SS);
   PORTB&=~(1<<SS);
}
ISR(SPI_STC_vect) //по прерыванию получаем от master значение counter
{
   while(~SPSR&(1<<SPIF)) //ждем завершения обмена данными
   ;
   counter=SPDR;//присваиваем переменной counter значение полученное по SPI и сохраненное в регистре данных SPI (SPDR)
}
int main(void)
{
    sei();
    SPI_slave_settings();
    _delay_ms(50);
    //тут должен располагаться код для инициализации и очистки OLED дисплея. Я его не пишу, чтобы сообщение не было слишком большим. Потому перехожу сразу к while
    while (1)
    {
        switch(counter) // выводим значение counter на OLED-дисплей
           {
      case 0:
         print_char('0');
         break;
      case 1:
         print_char('1');
         break;
      case 2:
         print_char('2');
         break;
      case 3:
         print_char('3');
         break;
      }
}
 

НО как только я отключаю кнопки от master и пытаюсь в нем менять переменную counter от 0 до 3 например в цикле for с задержкой 500мс, то slave на дисплее отображает цифры в другом порядке: 2,1,3. Код slave при этом не менялся.
Вот второй вариант кода main для master:
Код:
int main(void)
{
    SPI_master_settings();
    DDRC&=~((1<<3)|(1<<2)|(1<<1));//кнопки
    PORTC|=(1<<3)|(1<<2)|(1<<1);
    while (1)
    {
       for(int i=0;i<4;++i)
       {
      switch (i)
      {
      case 0: //отправляем 0
         SPDR = 0;
         PORTB &= ~(1<<SS);
         while(!(SPSR&(1<<SPIF))); //Дождаться окончания передачи
         PORTB |=(1<<PB2); //Установить "1" на линии SS
         break;
      case 1: [color=#40FF00]//отправляем 1
         SPDR = 1;
         PORTB &= ~(1<<SS);
         while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
         PORTB |=(1<<PB2); //Установить "1" на линии SS
         break;
      case 2: //отправляем 2
         SPDR = 2;
         PORTB &= ~(1<<SS);
         while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
         PORTB |=(1<<PB2); //Установить "1" на линии SS
         break;
      case 3: //отправляем 3
         SPDR = 3;
         PORTB &= ~(1<<SS);
         while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
         PORTB |=(1<<PB2); //Установить "1" на линии SS
         break;
      }
      _delay_ms(500);
      }
   }
}

Почему так? Пробовал реализовать SPI через прерывания и без них. Результат тот же. Пробовал в master менять переменную от 0 до 3, прибавляя +1 при нажатии на кнопку (или просто инкрементируя counter в цикле while) и снова тот же результат.
Пробовал убрать OLED-дисплей и подключить к slave три светодиода, чтобы они загорались по очереди в соответствии с переменной counter (т.к. сперва 1й, потом 2й, потом 3й). Но снова неудача и загорались как будто я передавал цифры не 1,2,3 а в порядке 2,1,3

Re: Atmega8. SPI не всегда верно работает

Вт апр 23, 2024 12:26:23

Код:
     
SPDR = 1;
PORTB &= ~(1<<SS);
while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
PORTB |=(1<<PB2); //Установить "1" на линии SS

а почему не в той последовательности ?
:roll:

согласно даташиту...
сначала надо Установить "0" на линии SS, а потом начинать передачу...

поэтому правильно будет так:
Код:
PORTB &= ~(1<<SS); //Установить "0" на линии SS
SPDR = 1; // Передаём "1"
while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
PORTB |=(1<<PB2); //Установить "1" на линии SS

это во первых))
:tea:

Добавлено after 18 minutes 50 seconds:
во вторых...

если мы используем прерывание
Код:
ISR(SPI_STC_vect) //по прерыванию получаем от master значение counter
{
   while(~SPSR&(1<<SPIF)) //ждем завершения обмена данными
   ;
   counter=SPDR;//присваиваем переменной counter значение полученное по SPI и сохраненное в регистре данных SPI (SPDR)
}

, то ждать завершения обмена данными не надо...

поэтому правильно будет так:
Код:
ISR(SPI_STC_vect) //по прерыванию получаем от master значение counter
{
   counter=SPDR;//присваиваем переменной counter значение полученное по SPI и сохраненное в регистре данных SPI (SPDR)
}

это во вторых))
:tea:

Добавлено after 8 minutes 47 seconds:
в третьих...

это:
Код:
 
  SPCR=0;
   SPSR=0;
   SPDR=0;
   SPCR|=(1<<SPIE)| (1<<SPE)| (1<<MSTR) ;
   SPCR|=(1<<SPE);//разрешаем работу SPI
   SPCR|=(1<<MSTR); // МК работает как master
   SPSR&=~(1<<SPI2X); //без удвоения частоты
   SPCR&=~(1<<SPR1);//нам нужен предделитель частоты МК clk/16:
   SPCR|=(1<<SPR0);
   SPCR|=(1<<CPOL) |(1<<CPHA); // импульс отрицательной полярности, задний фронт
   SPCR&=~(1<<DORD); //сперва передаются старшие биты

можно сократить до одной строчки))
Код:
SPCR=0x50;        // 8 МГц/4 = 2 МГц -режим мастер

:tea:

или можно подробнее... написать с комментариями))
Код:
// 0... .... SPIE разрешение прерывания...
// .1.. .... SPE разрешается работа SPI.
// ..0. .... DORD порядок сдвига данных DORD=0 первым передается старший разряд.
// ...1 .... MSTR (1-мастер, 0-слейв).
// .... 0... CPOL   0 = SCK имеет низкий уровень в состоянии ожидания. (полярность синхро).
// .... .0.. CPHA   0 = установка-задний фронт/выборка передий фронт SCK (фаза синхро).
// .... ..00 SPR1, SPR0      SCK = кварц 8 Мгц/4 = 2 МГц (частота синхро).
// .... ..00 SPR1, SPR0  f/4
// .... ..01 SPR1, SPR0  f/16
// .... ..10 SPR1, SPR0  f/64         
// .... ..11 SPR1, SPR0  f/128
SPCR=0x50;        // 8 МГц/4 = 2 МГц -режим мастер

это в третьих))
:tea:
и т.д.

Re: Atmega8. SPI не всегда верно работает

Вт апр 23, 2024 16:33:33

Зачем корчить из себя умного? Зачем повторять одну и ту же задачу:

Re: Atmega8. SPI не всегда верно работает

Вт апр 23, 2024 20:42:56

можно сократить до одной строчки))

Я только учусь и впервые столкнулся с SPI. Потому для наглядности расписываю подробно, чтобы спустя время, зайдя в программу знать какой бит для чего проставляется.

, то ждать завершения обмена данными не надо...

поэтому правильно будет так:

Это поправил в slave.

согласно даташиту...
сначала надо Установить "0" на линии SS, а потом начинать передачу...

поэтому правильно будет так:
Код:
PORTB &= ~(1<<SS); //Установить "0" на линии SS
SPDR = 1; // Передаём "1"
while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
PORTB |=(1<<PB2); //Установить "1" на линии SS


тоже поправил.
Решил чуть упростить и теперь main для master выглядит так:
Код:
int main(void)
{   
    SPI_master_settings();
    while (1)
    {
      for (int i=0;i<5;i++)
      {
         PORTB &= ~(1<<SS);
         SPDR = i;
         while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
         PORTB |=(1<<PB2); //Установить "1" на линии SS
         _delay_ms(300);
      }
    }
}

По идее master должен передавать в slave по очереди цифры 0,1,2,3,4. Но Slave почему то опять отображает цифры в порядке 0,2,4,1,3. Только в таком порядке. Хотя должно быть 0,1,2,3,4.
Пробовал в master последовательно изменять переменную, инкрементируя её при нажатии на кнопку, подключенную к PORTC.
Код:
if (~PINC&(1<<1))
      {
         while(~PINC&(1<<1))
         ;
         counter++;
         if(counter>4) counter = 0;
         
         PORTB &= ~(1<<SS);
         SPDR = counter;
         //while(!(SPSR&(1<<SPIF))) ; //Дождаться окончания передачи
         //PORTB |=(1<<PB2); //Установить "1" на линии SS
         _delay_ms(50);
      }

Но slave все равно выводил на дисплей цифры не в том порядке: 0,2,4,1,3 вместо 0,1,2,3,4. Не пойму почему он все путает. Slave выводил нужные цифры только когда я подключал 4 кнопки и по нажатии на каждую задавал конкретное значение переменной.
Я по итогу просто хочу чтобы slave выводил на дисплей цифру и увеличивал её по нажатию кнопки (подключенной к master) от 0 до 9. Но у меня почему то путается порядок передачи цифр. Может дело в инкрементации или ещё в чем? Вроде SPI не с чем конфликтовать , ведь я не использую другие прерывания

Re: Atmega8. SPI не всегда верно работает

Ср апр 24, 2024 12:33:54

Sharcer писал(а):Пробовал убрать OLED-дисплей и подключить к slave три светодиода, чтобы они загорались по очереди в соответствии с переменной

правильная мысль)) всё начинается с диодов...
:tea:
и где наш полный код с иcправлениями ?

Re: Atmega8. SPI не всегда верно работает

Ср апр 24, 2024 20:12:54

Sharcer писал(а):Пробовал убрать OLED-дисплей и подключить к slave три светодиода, чтобы они загорались по очереди в соответствии с переменной

правильная мысль)) всё начинается с диодов...
:tea:
и где наш полный код с иcправлениями ?

ВСЁ!!!!!! ЗАРАБОТАЛО!!! Цифры от 0 до 3 передаются нормально по очереди каждые 300мс и slave зажигает светодиод, соответствующий номеру этой цифры! В общем уже не знаю каким образом я к этому пришел, но вот мои исправления:
1) в master передаваемую переменную объявил не в начале программы (там где все define F_CPU и т.д.) , а внутри main
2) в slave получаемая переменная counter по прежнему объявлена в начале программы (там где все define F_CPU и т.д.) , НО БЕЗ значения по умолчанию (просто "int counter;")

Вот полный код master:
Код:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define SS 2
#define MOSI 3
#define MISO 4
#define SCK 5

void SPI_master_settings(void) //настройки  SPI_Master
{
   
   DDRB|=(1<<MOSI)|(1<<SCK)|(1<<SS);
   PORTB|=(1<<SS); //этот пин надо установить раньше настроек регистра контроля т.е. раньше команды SPCR|=(1<<MSTR);
   DDRB&=~(1<<MISO);
   PORTB|=(1<<MOSI)|(1<<SCK)|(1<<SS);
   //PORTB&=~(1<<SS);
   
   SPCR=0;
   SPSR=0;
   SPDR=0;
   
   SPCR|=(1<<SPIE);//SPCR-регистр контроля SPI.
   
   SPCR|=(1<<SPE);//SPI ENABLE.
   SPCR|=(1<<MSTR); // МК работает как master
   
   
   SPSR&=~(1<<SPI2X); // мы не удваиваем частоту работы SPI
   SPCR&=~(1<<SPR1);
   SPCR|=(1<<SPR0);
   
   SPCR|=(1<<CPOL); //мы используем импульсы отрицательной полярности согласно
   SPCR|=(1<<CPHA);//работаем по заднему фронту импульса
   SPCR&=~(1<<DORD); //сперва передаются старшие биты(MSB),  а потом младшие (LSB)
}
int main(void)
{
     while(1)
     {
           for (int i=0;i<4;i++)
      {
         PORTB &= ~(1<<SS);
         SPDR = i;
         while(!(SPSR&(1<<SPIF))) //Дождаться окончания передачи
         ;
         PORTB |=(1<<SS); //Установить "1" на линии SS
         _delay_ms(300);
      }
     }
}

Вот код slave:
Код:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h> //для работы с PROGMEM
unsigned int r1_1000, r2_100=0, r3_10=0,  r4_1=0;

#define SS 2
#define MOSI 3
#define MISO 4
#define SCK 5

int counter;
void SPI_slave_settings(void) //настройки  SPI_SLAVE
{
   DDRB&=~((1<<MOSI)|(1<<SCK));
   DDRB|=(1<<MISO);
   PORTB|=(1<<MISO);
   
   SPCR=0;
   SPSR=0;
   SPDR=0;
   
   SPCR|=(1<<SPIE);
   SPCR|=(1<<SPE);
   SPCR&=~(1<<MSTR); // МК работал как slave
   
   //мы не выбираем частоты работы SPI для slave, т.к. он подчиняется master
   
   SPCR|=(1<<CPOL); //мы используем импульсы отрицательной полярности
   SPCR|=(1<<CPHA);//работаем по заднему фронту импульса
   SPCR&=~(1<<DORD);
   
   DDRB&=~(1<<SS);
   PORTB&=~(1<<SS);
}

ISR(SPI_STC_vect)
{
   counter=SPDR;//присваиваем переменной counter значение полученное по SPI и сохраненное в регистре данных SPI (SPDR)
}
int main(void)
{
    sei();
    SPI_slave_settings();
   DDRC|=(1<<1)|(1<<0);//1-й и 2-й светодиоды
   PORTC&=~((1<<5)|(1<<4));
   DDRB|=(1<<7)|(1<<6);//3-й и 4-й светодиоды
   PORTB&=~((1<<7)|(1<<6));
 while (1)
    {
                switch(counter)
      {
         
         case 0:
            PORTC&=~((1<<1)|(1<<0));PORTB&=~((1<<7)|(1<<6));
            break;
         case 1:
            PORTC|=(1<<0);PORTC&=~(1<<1);PORTB&=~((1<<7)|(1<<6));
            break;
         case 2:
            PORTC|=(1<<1);PORTC&=~(1<<0);PORTB&=~((1<<7)|(1<<6));
            break;
         case 3:
            PORTB|=(1<<6);PORTB&=~(1<<7);PORTC&=~((1<<1)|(1<<0));
            break;
         case 4:
            PORTB|=(1<<7);PORTB&=~(1<<6);PORTC&=~((1<<1)|(1<<0));
            break;
      }
     }
}

Re: Atmega8. SPI не всегда верно работает

Чт апр 25, 2024 11:48:43

Sharcer писал(а):1) в master передаваемую переменную объявил не в начале программы (там где все define F_CPU и т.д.) , а внутри main

-если объявить переменную в функции (например main), то и использовать эту переменную можно только внутри функции (например main).
Sharcer писал(а):2) в slave получаемая переменная counter по прежнему объявлена в начале программы (там где все define F_CPU и т.д.) , НО БЕЗ значения по умолчанию (просто "int counter;")

-если объявить переменную в начале программы (там где все define F_CPU и т.д.), то и использовать эту переменную можно любом месте программы.
:roll:
поэтому я все переменные объявляю в начале программы (там где все define F_CPU и т.д.) и использую все переменные в любом месте программы.
:tea:
unsigned char x = 0;
unsigned int y = 0;
unsigned long z = 0;
...
Ответить