Страница 1 из 3

програмный SPI

Добавлено: Вс янв 03, 2021 12:32:08
sergo80zxc
Приветствую товарищи радиокоты).
Писал программную реализацию SPI протокола для атмеги 168, для мастера , пины как в аппаратном, в протеусе работает, а в железе наотрез отказывается.
Слейв тоже 168 мега, но задействован аппаратный SPI ( режим 0).
Есть у кого какие мысли почему в железе не работает???


программный, мастер

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

/*
 * GccApplication37.c
 *
 * Created: 03.01.2021 0:42:56
 *  Author: сергей
 */ 


//
//

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL
#include <util/delay.h>           // Подключаем библиотеку функций задержки ( для использования функций задержки например _delay_ms(10);     // задержка 10 милисекунд  
 
//-------------------------------------------------- ПОРТ (SPI), НАСТРОЙКА ВХОДОВ И ВЫХОДОВ -----------------------------------------------------------------------------

#define DDR_SPI DDRB                              // обзовем порт (DDRB) удобным именем (DDR_SPI)
#define DD_MOSI DDB3                              // обзовем пин  (DDB3) удобным именем (DD_MOSI)
#define DD_SCK  DDB5                              // обзовем пин  (DDB5) удобным именем (DD_SCK)
#define DD_SS   DDB2                              // обзовем пин  (DDB2) удобным именем (DD_SS)
#define DD_MISO DDB4                              // обзовем пин  (DDB4) удобным именем (MISO_SS)

//---------------------------------------------------ПОРТ (SPI), НАСТРОЙКА РАБОЧИХ ПИНОВ ---------------------------------------------------------------------------------

#define PORT_SPI  PORTB                           // обзовем порт (PORTB)  удобным именем (PORT_SPI)
#define PB_SS     PORTB2                          // обзовем пин  (PB2)    удобным именем (PB_SS)
#define PB_SCK    PORTB5                          // обзовем пин  (PB5)    удобным именем (PB_SCK)
#define PB_MOSI   PORTB3                          // обзовем пин  (PB3)    удобным именем (PB_MOSI)
#define PB_MISO   PORTB4                          // обзовем пин  (PB4)    удобным именем (PB_MISO)


#define PIN_SPI   PINB                            // PINB  обзавем PIN_SPI  ( тут буит проверка бита и все таккое)))
#define PIN_MISO  PINB4                           // обзовем пин  (PINB4)    удобным именем (PIN_MISO)

//-------------------------------------------------------- МАКРОС  ВКЛЮЧЕНИЯ И ОТКЛЮЧЕНИЯ ВЫВОДА (SS) МОДУЛЯ  (SPI) -----------------------------------------------------

#define macro_START_SLAVE() PORT_SPI &= ~(1 << PB_SS)   // старт ведомому МК ( активизируем ведомый МК  на прием и передачу данных сбросив в 0 пин и линию (SS))
#define macro_STOP_SLAVE()  PORT_SPI |= (1 << PB_SS)    // стоп ведомому МК ( дезактивизируем ведомый МК отключим прием и передачу данных установив в 1 пин и линию (SS))
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

#define PORT_LED PORTD                            // PORTD обзавем PORT_LED ( тут индикация будет и все такое))))
#define DDR_LED DDRD                              // DDRD  обзавем DDR_LED  ( тут настройка порта на выход буит и все таккое)))


volatile uint8_t sis_taimer =0;                   // объявим  переменную системного таймера

//-----------------------------------------------------------------------------------------------------------------------------------------------------------
void TIMER0_init(void)                        // инициализация и настройка модуля таймера Т0
{
	TIMSK0 = 0b00000001;       // бит РВ0(TOIE0) устанавливаем в 1, разрешения прерывания по переполнению таймера /счетчика Т0
	TCCR0A = 0b00000000;       // биты (WGM02,WGM01,WGM00) сбрасываем в 0,  настройка режима Normal
	TCCR0B = 0b00000100;       // настраиваем предделитель на 256 (биты  CS02=1,CS01=0,CS00=0),бит В3(WGM02) сбрасываем в 0,режим Normal
	GTCCR  = 0b00000001;       // бит РВ0(PSRSYNC) устанавливаем в 1, Сброс предделителя TO (сбрасываем пред. в конце настроек)
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
void SPI_MasterInit(void)                     // инициализация и настройка модуля SPI
{
	DDR_SPI |= (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);   // MOSI, SCK и SS установим на выход  так как у нас МК в режиме МАСТЕР
	                                                  // MISO на вход , по умолчанию инициализируется нулем													  													  													  
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
ISR(TIMER0_OVF_vect)                          // прерывание по переполнению таймера
{
	sis_taimer ++;                        // инкрементируем наш системный таймер
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t SPI_send_and_receive_bytes (uint8_t data)    // функция программного SPI. входные параметры байт для передачи (data), полученый байт возвращаем в вызывающюю функцию
{
    uint8_t a = 7;                // переменая (a) номер бита байта (data) вначеле цикла он равен 7 ( с 7 старшего бита начнем передачу)
    uint8_t b = 7;                // переменая (b) номер бита  байта (out_bytes) в нее будем читать полученый бит, он равен 7 ( с 7 старшего бита принимаем биты от слейва)	
	uint8_t out_bytes = 0;        // создадим переменную (out_bytes), через нее  будем возвращать принятый байт в вызывающюю функцию
                                  // режим 0,  CPOL=0  сигнал синхронизации начинается с низкого уровня,  CPHA=0  чтение битов по переднему фронту
                                                                                								 
	macro_START_SLAVE();          // старт ведомому МК ( активизируем ведомый МК  на прием и передачу данных сбросив в 0 пин и линию (SS))
			
    for (uint8_t i = 0; i < 8; i++)      // цикл "для", повторяющийся 8 раз
    {    	   

          _delay_ms(2);                  // задержка 10 милисекунд

       if (data & (1 << a))               // проверяем  бит (a) переменной (data), если этот бит = 1, то выполняется КОД 1. (выражение в скобках)
       {  PORT_SPI |= (1<<PB_MOSI);       // устанавливаем пин (PB_MOSI) в 1. то есть у нас в переменной (data) в этом бите 1
       }
       else                               // иначе если (a) бит = 0 , то выполняем КОД 2.
       {  PORT_SPI &= ~(1 << PB_MOSI);    // сбрасываем пин (PB_MOSI) в 0. то есть у нас в переменной (data) в этом бите 0
       }
          a-- ;                           // декрементируем номер бита (a) байта (data)		  
          _delay_ms(2);                  // задержка 10 милисекунд
		  
          PORT_SPI |= (1<<PB_SCK);        // пин (SCK) установим в 1, нарастающий фронт)
		  
	        asm("NOP");                    // пустая операция 
			                 				  		           
       if (PIN_SPI & (1 << PIN_MISO))     // проверяем  бит (PIN_SPI) порта (PIN_SPI), если этот бит = 1, то выполняется КОД 1. (выражение в скобках)
       {   out_bytes |= (1<<b);           // устанавливаем бит (b) байта (out_bytes) в 1. 
       }
       else                               // иначе если бит (PB_MISO) = 0 , то выполняем КОД 2.
       {   out_bytes &= ~(1 << b);         // сбрасываем пин (b)  байта (out_bytes) в 0.
       }
          b-- ;                           // декрементируем номер бита (b) байта (out_bytes)       

          _delay_ms(2);                  // задержка 10 милисекунд
          _delay_ms(2);                  // задержка 10 милисекунд
       
          PORT_SPI &= ~(1<<PB_SCK);     // пин (SCK) сбросим в 0,( начался  период такта синхронизации. )		  
    } 
	        asm("NOP");                    // пустая операция            
	      macro_STOP_SLAVE();             // стоп ведомому МК	
          return (out_bytes);             // возвращаем значение полученого байта от слейва в вызывающюю функцию	
	                           	
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
int main(void)
{
	DDR_LED  =  0b11111111;           // на выход ( на нем наши индикаторные светодиодики)))
	TIMER0_init ();                   // инициализация и настройка модуля таймера Т0	
	SPI_MasterInit ();                // инициализация и настройка модуля SPI
    volatile  uint8_t XXX;            // в эту переменную будет возвращатся значение из функции (SPI_send_and_receive_bytes)
    PORT_SPI &= ~(1<<PB_SCK);         // пин (SCK) сбросим в 0,( режим ожидания )   
	sei();                            // Разрешить прерывания
	
	while (1)                         // Основной цикл программы
	{
		
		
        if (sis_taimer ==10)     // системный таймер 
        {
            XXX = SPI_send_and_receive_bytes (0b10101010);  // присваиваем переменной (XXX) возвращаемое значение функции (SPI_send_and_receive_bytes)    
	        asm("NOP");                    // пустая операция 	  
	   
	        PORT_LED = XXX;       // принятый байт выведем в порт индикации
	   
	        sis_taimer =0;        // сбросим таймер и ждем следующего тика системного таймера
        }		
	  		
	        asm("NOP");                    // пустая операция 
	}
}


аппаратный, слейв, проверен в работе.

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

/*
 * GccApplication18.c
 *
 * Created: 29.10.2020 16:49:11
 *  Author: сергей
 */ 


//   -------------------------------------------------------------- ИЗУЧАЕМ SPI --------------------------------ЧЕРЕЗ ПРЕРЫВАНИЕ ПО SPI
// ПРОГА для прошивки мега168р в режиме SLAVE---------------------------------------------------------ПРОЕКТ 5--------------------------
// отправляем мастеру бегущий огонек по команде мастера.
//
// передаем мастеру массив побайтно и по кругу
//
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL

//-------------------------------------------------- НАСТРОЙКА ПОРТА (SPI) ----------------------------------------------------------------------------------------------

#define DDR_SPI DDRB                              // обзовем порт (DDRB)  удобным именем (DDR_SPI)
#define DD_MISO DDB4                              // обзовем пин (DDB4) удобным именем (MISO_SS)
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

#define PORT_LED PORTD                            // PORTD обзавем PORT_LED ( тут индикация будет и все такое))))
#define DDR_LED DDRD                              // DDRD  обзавем DDR_LED  ( тут настройка порта на выход буит и все таккое)))

//-----------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t go [8] =                // объявим массив (go) на 8 элементов иинициализируем каждый элемент числом
{
	0b00000001,       // загоратся будет пин (PD0)
	0b00000010,       // загоратся будет пин (PD1)
	0b00000100,       // загоратся будет пин (PD2)
	0b00001000,       // загоратся будет пин (PD3)
	0b00010000,       // загоратся будет пин (PD4)
	0b00100000,       // загоратся будет пин (PD5)
	0b01000000,       // загоратся будет пин (PD6)
	0b10000000,       // загоратся будет пин (PD7)
};
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
void SPI_SlaveInit(void)                    // инициализация и настройка модуля SPI МК в режиме (Slave)
{
	DDR_SPI |= (1<<DD_MISO);                // MISO установим на выход  так как у нас МК в режиме SLAVE
	                                        // MOSI, SCK и SS на вход , по умолчанию инициализируется нулем
	
	SPCR |= (1<<SPE)|(1<<SPR0)|(1<<SPR1)|(1<<SPIE);   // установим бит (SPE) в 1 включим модуль SPI,  бит (MSTR) сброшен в 0 включим режим (Slave),
	                                                  // (SPR0) и (SPR1)  установим в 1  режим скорости fclk/128,  (DORD) сброшен в 0 и первым
	                                                  // передается старший (седьмой) бит, (CPOL) сброшен в 0 и на линии SCK во время ожидания установлен низкий логический
}	                                                  // уровень,(CPHA) сброшен в 0 и данные считываются по нарастающему фронту сигнала,(SPIE) в 1 Разрешение прерывания от SPI
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t running_light(void)           // функция передает в вызывающюю функцию бегущий огонек (при каждом вызове в переданом байте еденица смещается на разряд влево)
{
	uint8_t data = 0;           // создаем и задаем значение  переменной (data),
	static uint8_t z=0;         // статическая переменная (z) , она у нас будет порядковым номером массива (go)
	data = go [z];              // отправляем в переменную (data) элемент массива (go) с порядковым номером  (z)
	z++;                        // инкрементируем переменную (z) чтобы в следующей интерации в переменную (data) поступил следующий по счету элемент массива
	if (z>7) z=0;               // защита от переполнения массива (go)
	return (data);              // передаем в вызывающюю функцию наш байт
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
ISR(SPI_STC_vect)                            // прерывание по завершению передачи байта
{
	PORT_LED = SPDR;                         // читаем (SPDR) и выводим полученый байт в порт (PORT_LED)
	SPDR = running_light();                  // запишем в дата регистр (SPDR)  байт полученый из функции  (running_light)
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------

int main(void)
{
	DDR_LED  =  0b11111111;              // на выход ( на нем наши индикаторные светодиодики)))
	SPI_SlaveInit ();                    // инициализация и настройка модуля SPI МК в режиме (Slave)
	SPDR = 0b11110000;                   // запишем в регистр данных (SPDR) число 0, его считает мастер при первом обмене байтами по SPI
	sei();                               // Разрешить прерывания
	
	while (1)                            // Основной цикл программы
	{
		asm("NOP");                       // пустая операция
	}
}
в железе на мастере загораются пины 4-7 ( то есть мастер получил один байт от слейва и дальше не может почему то получать)

Re: програмный SPI

Добавлено: Вс янв 03, 2021 12:57:38
Eddy_Em
Когда коту делать нечего, он программный SPI реализует!..
ЗАЧЕМ? АВРка — то еще дерьмо, какой, нафиг, программный SPI?
Вы еще CAN программный запилите. И USB-HS программный…

Re: програмный SPI

Добавлено: Вс янв 03, 2021 13:02:12
sergo80zxc
Eddy_Em, ну я изучаю SPI))) с аппаратным все вроде просто и понятно, а вот на программном затроил. аврки просто боле менее знакомы

Re: програмный SPI

Добавлено: Вс янв 03, 2021 13:06:54
NStorm
Купите логический анализатор за ~600р на CY8, если еще нет. Незаменимая вещь в отслеживании подобных проблем в железе.

Re: програмный SPI

Добавлено: Вс янв 03, 2021 13:07:16
Eddy_Em
sergo80zxc, программный UART, I2C, SPI и т.п. — это просто маразм чистой воды!
Если нет аппаратного, значит, микроконтроллер выбран неправильно!!!

Re: програмный SPI

Добавлено: Вс янв 03, 2021 13:25:36
sergo80zxc
Eddy_Em, согласитесь все же может понадобится в каких то случаях)

Re: програмный SPI

Добавлено: Вс янв 03, 2021 14:43:08
Eddy_Em
sergo80zxc, только в экстраординарных, когда "очень надо" написать прошивку для готовой железяки, а проектировал ее идиот…
Но на мой взгляд, в данном случае быстрей будет спроектировать с нуля новую железяку правильно. Да и по деньгам выгодней будет: дороже почасовка выйдет.

Re: програмный SPI

Добавлено: Вс янв 03, 2021 15:52:22
Самсусамыч
[uquote="Eddy_Em",url="/forum/viewtopic.php?p=3952727#p3952727"]Если нет аппаратного, значит, микроконтроллер выбран неправильно!!![/uquote]
Много чего приходилось читать, но такой бред впервые. :)))

Re: програмный SPI

Добавлено: Вс янв 03, 2021 16:06:27
sergo80zxc
так то понимаю физику процесса, вроде ничего космического тут нет в программном исполнении... после спада сигнала скк ждем полпериода его нулевого значения, затем пишем отправляемый бит в моси, ждем еще полпериода нулевого значения и выставляем скк,тут же читаем присланный бит с мисо и ждем уже весь период единичного значения, затем сбрасываем скк в ноль - это один цикл приемопередачи, их 8 , вот и вся функция приемопередачи

но в железе не работает,может где какая глупая ошибка?

Re: програмный SPI

Добавлено: Вс янв 03, 2021 16:44:44
Eddy_Em
[uquote="Самсусамыч",url="/forum/viewtopic.php?p=3952835#p3952835"]Много чего приходилось читать, но такой бред впервые. :)))[/uquote]
Бред — это брать МК без аппаратного SPI/I2C/etc, а потом мучить его софтовым. Маразм крепчал и крыша отъезжала!..

Re: програмный SPI

Добавлено: Вс янв 03, 2021 19:06:27
Самсусамыч
[uquote="sergo80zxc",url="/forum/viewtopic.php?p=3952842#p3952842"]но в железе не работает,может где какая глупая ошибка?[/uquote]
Если не работает, значит есть ошибка… если по простому - на пальцах, то ждать там некого… Спецификация SPI имеет 4 режима передачи данных… если рассматривать первый (обработка данных производится по переднему фронту импульсов сигнала SCK), то когда на линии SCK лог 0, оба МК формируют на своих выходах очередной бит. А при SCK лог 1 читают входа, сохраняя очередной принятый бит в своих регистрах. Вот собственно и всё. Если подробней описать один из вариантов этого процесса передачи данных мастером, то выглядит это так: Назовём один из рабочих регистров Loop (петля) – это регистр определяющий количество передаваемых бит. Следующий регистр назовём Data_SPI (данные SPI) – в этот регистр будет загружаться байт для передачи и в него же будут записываться биты при приёме. Теперь по пунктам:
1. На выходе SS формируем лог 0 – что указывает ведомому о готовности к передаче данных по шине.
2. В регистр Loop записываем число 8.
3. Устанавливаем лог 0 на выводе MOSI.
4. Сдвигаем влево данные регистра Data_SPI. Это означает, что передаём старшим битом вперёд.
5. Проверяем состояние бита C в регистре SREG. Если бит С равен лог 0, то переходим к пункту 7. Если бит С равен лог 1, то переходим к пункту 6.
6. Устанавливаем лог 1 на выводе MOSI.
7. Устанавливаем лог 1 на выводе SCK.
8. Затем проверяем вход MISO. Если на нём лог 0, то переходим к пункту 10. Если лог 1, то переходим на пункт 9.
9. Прибавляем к регистру Data_SPI единицу. Не записываем в него 1, а именно прибавляем.
10. Устанавливаем лог 0 на выводе SCK.
11. Отнимаем единицу из регистра Loop с проверкой нуля. Если не ноль, то переходим в пункт 3. Если ноль, то к пункту 12.
12. Устанавливаем лог 1 на выводе SS.
Передача байта по SPI завершена. И можно забрать при необходимости из регистра Data_SPI данные переданные ведомым устройством.

Добавлено after 34 minutes 32 seconds:
[uquote="Eddy_Em",url="/forum/viewtopic.php?p=3952849#p3952849"]Маразм крепчал и крыша отъезжала!..[/uquote]
Сочувствую.

Re: програмный SPI

Добавлено: Вс янв 03, 2021 20:04:53
sergo80zxc
хотелось бы понять в моем коде что не так

Re: програмный SPI

Добавлено: Вс янв 03, 2021 20:24:39
Аlex
Eddy_Em, завязывайте тут слюнями брызгать. Идите проспитесь.
хотелось бы понять в моем коде что не так
Вам же всё расписали по полочкам, сравнивайте :)

Добавлено after 17 minutes 39 seconds:
Сейчас вбил в поисковик "SPI интерфейс" и, по мимо множества статей, наткнулся даже на видео https://yandex.ru/efir?stream_id=vB1hVVu6t_QE.
А Вы пробовали это делать ? :)

Re: програмный SPI

Добавлено: Вс янв 03, 2021 20:33:56
Самсусамыч
[uquote="sergo80zxc",url="/forum/viewtopic.php?p=3952948#p3952948"]в моем коде что не так[/uquote]
Я не знаю языка Си (пишу на другом…), потому написал по пунктам для понимания не зависимо от языка программирования. :)

Re: програмный SPI

Добавлено: Вс янв 03, 2021 22:05:38
Eddy_Em
Аlex, хватит уже маразмом полыхать!
Если уж так хочется BDSM, так сделайте его по-человечески: таймер генерирует импульсы CLK на одной ноге, а двумя другими работает с MOSI и MISO! Все реализуется через DMA "почти аппаратно".
Но все равно это — идиотизм чистой воды! Есть аппаратный SPI — так и используйте его. Нет — берите МК, где он есть!!!


Я предупреждал. Споры с Вами завязывать не буду.
Отдохните недельку. Ещё раз повторите свои выпады - получите вечный бан.
Alex.

Re: програмный SPI

Добавлено: Пн янв 04, 2021 00:47:52
Oxford
sergo80zxc я ваш код проверил на аппаратуре STM32 c VS1063.

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

Передача работает правильно. Прием байта не проверял так как у меня прием не использовался.
Изображение

Re: програмный SPI

Добавлено: Пн янв 04, 2021 01:08:48
sergo80zxc
Oxford, спасибо огромное) наверное скорости слейва и мастера разные вот и не работало) предположил) попробую подогнать поближе)

Re: програмный SPI

Добавлено: Пн янв 04, 2021 01:24:49
Oxford
интерфейс синхронный, мастер задает клок.

Re: програмный SPI

Добавлено: Пн янв 04, 2021 01:33:26
sergo80zxc
Oxford, да, согласен, в слейве ради приличия скорость настраивается)) попробовал делаи убрать опять в железе не работает, только первый байт от слейва приходит и все, буду копать..)

Re: програмный SPI

Добавлено: Пн янв 04, 2021 12:12:31
NStorm
Никакого приличия )
These two bits control the SCK rate of the device configured as a Master. SPR1 and SPR0 have no effect on the Slave.