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

Обсуждаем контроллеры компании Atmel.
Ответить
Sharcer
Родился
Сообщения: 15
Зарегистрирован: Сб окт 07, 2023 21:34:17

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

Сообщение Sharcer »

Есть два МК 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
Реклама
roman.com
Друг Кота
Сообщения: 9149
Зарегистрирован: Вт мар 13, 2012 12:16:13
Откуда: .ru

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

Сообщение roman.com »

Код: Выделить всё

     
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:
и т.д.
Реклама
OKF
Это не хвост, это антенна
Сообщения: 1393
Зарегистрирован: Вт июн 07, 2011 08:03:18

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

Сообщение OKF »

Зачем корчить из себя умного? Зачем повторять одну и ту же задачу:
Sharcer
Родился
Сообщения: 15
Зарегистрирован: Сб окт 07, 2023 21:34:17

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

Сообщение Sharcer »

[uquote="roman.com",url="/forum/viewtopic.php?p=4572102#p4572102"]можно сократить до одной строчки))[/uquote]
Я только учусь и впервые столкнулся с 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 не с чем конфликтовать , ведь я не использую другие прерывания
Реклама
Эиком - электронные компоненты и радиодетали
roman.com
Друг Кота
Сообщения: 9149
Зарегистрирован: Вт мар 13, 2012 12:16:13
Откуда: .ru

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

Сообщение roman.com »

Sharcer писал(а):Пробовал убрать OLED-дисплей и подключить к slave три светодиода, чтобы они загорались по очереди в соответствии с переменной
правильная мысль)) всё начинается с диодов...
:tea:
и где наш полный код с иcправлениями ?
Реклама
Sharcer
Родился
Сообщения: 15
Зарегистрирован: Сб окт 07, 2023 21:34:17

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

Сообщение Sharcer »

[uquote="roman.com",url="/forum/viewtopic.php?p=4572589#p4572589"]
Sharcer писал(а):Пробовал убрать OLED-дисплей и подключить к slave три светодиода, чтобы они загорались по очереди в соответствии с переменной
правильная мысль)) всё начинается с диодов...
:tea:
и где наш полный код с иcправлениями ?[/uquote]
ВСЁ!!!!!! ЗАРАБОТАЛО!!! Цифры от 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;
		}
     }
}
Реклама
roman.com
Друг Кота
Сообщения: 9149
Зарегистрирован: Вт мар 13, 2012 12:16:13
Откуда: .ru

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

Сообщение roman.com »

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;
...
Ответить

Вернуться в «AVR»