програмный SPI

Обсуждаем контроллеры компании Atmel.
Ответить
Друг Кота
Сообщения: 9175
Зарегистрирован: Вт мар 13, 2012 12:16:13
Откуда: .ru

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

Программный SPI для AVR делается очень просто. Например вот мой программный SPI:


////////////////////////////// программный SPI:
unsigned char x; // x - Data SPI
unsigned char tx; // tx - SPI
unsigned char rx; // rx - SPI
unsigned char mask; // mask - SPI
////////////////////////////// функция программный SPI:
void SPI(void)
{
rx=0; // сброс RX
mask=0b10000000;
do {
if (tx & mask) PORTA.1 = 1;// выход MOSI
else PORTA.1 = 0;// выход MOSI
delay_us(2);
PORTA.3=1; // выход SCK
if (PINA.0) rx |= mask; // вход MISO
PORTA.3=0; // выход SCK
mask=(mask>>1);
}
while (mask);
delay_us(2);
PORTA.1 = 0; // сброс MOSI
}



void main(void)
{
////////////////////////////////////////////// программный SPI:
DDRA.0=0; // MISO
PORTA.0=0; // MISO
DDRA.1=1; // MOSI
PORTA.1=0; // MOSI
DDRA.2=1; // SS
PORTA.2=1; // SS
DDRA.3=1; // SCK
PORTA.3=0; // SCK

////////////////////////////////////////////// TX, RX - Data SPI
tx=x; SPI(); x=rx;
//////////////////////////////////////////////

}

Всё работает. И в протеусе и в железе.
:tea:


Намного сложней сделать программный Ethernet.
:roll:

Добавлено after 18 minutes 8 seconds:
P.S.
Выше - это для всяких LCD экранчиков.
А для связи двух AVR надо добавить вывод SS (обязательно!). Тогда будет так:

////////////////////////////// программный SPI:
unsigned char x; // x - Data SPI
unsigned char tx; // tx - SPI
unsigned char rx; // rx - SPI
unsigned char mask; // mask - SPI
////////////////////////////// функция программный SPI:
void SPI(void)
{
rx=0; // сброс RX
mask=0b10000000;
do {
if (tx & mask) PORTA.1 = 1;// выход MOSI
else PORTA.1 = 0;// выход MOSI
delay_us(2);
PORTA.3=1; // выход SCK
if (PINA.0) rx |= mask; // вход MISO
PORTA.3=0; // выход SCK
mask=(mask>>1);
}
while (mask);
delay_us(2);
PORTA.1 = 0; // сброс MOSI
}



void main(void)
{
////////////////////////////////////////////// программный SPI:
DDRA.0=0; // MISO
PORTA.0=0; // MISO
DDRA.1=1; // MOSI
PORTA.1=0; // MOSI
DDRA.2=1; // SS
PORTA.2=1; // SS
DDRA.3=1; // SCK
PORTA.3=0; // SCK

////////////////////////////////////////////// TX, RX - Data SPI
PORTA.2=0; // SS
tx=x; SPI(); x=rx;
PORTA.2=1; // SS
//////////////////////////////////////////////

}

А давайте лучше сделаем программный Ethernet. Он интересней )) :tea:
Реклама
Встал на лапы
Аватара пользователя
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Сообщение sergo80zxc »

оееой по ходу мои две отладочные платы с мегами нагнулись, не работают рабочие прошивки, исправлю напишу)
глаза боятся, а руки что то не делают))
Реклама
Первый раз сказал Мяу!
Сообщения: 30
Зарегистрирован: Сб дек 17, 2016 21:14:01

Сообщение godrik123 »

Не хочу плодить темы, столкнулся с такой проблемой.
Есть slave устройство, конкретно контроллер сенсорной клавиатуры TTP229. При нажатии на кнопку, контроллер генерирует кратковременный (80us) импульс на линии MISO, ждет от мастера тактовые импульсы, и по наличию тактов на линии clock, двигает биты состояния кнопок. Так вот я не очень понял, как в таком случае master (mega8) должен отслеживать этот импульс? Разве аппаратно в SPI контроллера это реализовано? Я так понимаю нужно либо использовать прерывание по изменению пина, либо вручную проверять состояние?
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="godrik123",url="/forum/viewtopic.php?p=3959330#p3959330"]Я так понимаю нужно либо использовать прерывание по изменению пина, либо вручную проверять состояние?[/uquote]
Да.
Реклама
Эиком - электронные компоненты и радиодетали
Мудрый кот
Сообщения: 1759
Зарегистрирован: Пт июн 01, 2018 07:28:45

Сообщение parovoZZ »

[uquote="godrik123",url="/forum/viewtopic.php?p=3959330#p3959330"]Так вот я не очень понял, как в таком случае master (mega8) должен отслеживать этот импульс? Разве аппаратно в SPI контроллера это реализовано? Я так понимаю нужно либо использовать прерывание по изменению пина, либо вручную проверять состояние?[/uquote]
В AVR регистр IN всегда подключен к внешнему выводу (его можно отключить для исключения шумов от триггера Шмитта, если этот вывод подключён к АЦП ). Поэтому разрешаем прерывание по этому пину (точнее, порту. Если пинов с прерваниями на этом порту несколько, то в обработчике прерывания по маске на регистр флагов вычисляем сработавший пин), в обработчике его запрещаем и продолжаем работать с SPI.
Реклама
Первый раз сказал Мяу!
Сообщения: 30
Зарегистрирован: Сб дек 17, 2016 21:14:01

Сообщение godrik123 »

Спасибо, просто у меги 8 нету внешнего прерывания pcint, только int0/int1, то есть на 2 ноги всего. Ну не важно, просто удивила сама концепция контроллера сенсорных кнопок, довольно неудобно, нужно зайдествовать и spi и внешнее прерывание, или нырять в прерывание по таймеру каждые 50us.
Реклама
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Сообщение NStorm »

godrik123, ну мега8 - мягко говоря старый МК. У чуть более современных обычно почти любую ногу можно настроить на прерывание по изменению уровня хотя бы. Ну а как бы оповещения мастера слэйвом о новых данных и вообще часто на отдельный пин прерывания выносятся. Как еще вы предлагаете слэйву сообщить о том, что было что-то нажато, если обмен SPI может идти только при наличии клока от мастера? Тогда мастеру всё-время клок генерить надо и не спать. В общем вполне стандартное решение.

Добавлено after 4 minutes 31 second:
Взяли бы atmega88 или atmega168, вроде у китайцев они столько же стоят почти и в DIP корпусе есть если вдруг надо. Зато PCINT есть.
Мудрый кот
Сообщения: 1759
Зарегистрирован: Пт июн 01, 2018 07:28:45

Сообщение parovoZZ »

Что, и компаратора в МЕГА8 тоже нету?

Добавлено after 1 minute 13 seconds:
А для сенсорных кнопок я прикупил MSP430FR2512 по 30 рублей на распродаже у дилера.
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Сообщение NStorm »

parovoZZ, компаратор есть, всего один (всегда на паре пинов при этом, никаких возможностей переназначения нет), при этом из сна будить не умеет мегу8 всё-равно )
Мудрый кот
Сообщения: 1759
Зарегистрирован: Пт июн 01, 2018 07:28:45

Сообщение parovoZZ »

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

у меги 8 нету внешнего прерывания pcint, только int0/int1,
Так а я не пойму - они заняты или что? Объединить их с MISO и делов.

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

компаратор есть, всего один.....при этом из сна будить не умеет мегу8 всё-равно
не МК, а какая-то бедапечаль(
Первый раз сказал Мяу!
Сообщения: 30
Зарегистрирован: Сб дек 17, 2016 21:14:01

Сообщение godrik123 »

[uquote="NStorm",url="/forum/viewtopic.php?p=3959920#p3959920"]godrik123, ну мега8 - мягко говоря старый МК. У чуть более современных обычно почти любую ногу можно настроить на прерывание по изменению уровня хотя бы. Ну а как бы оповещения мастера слэйвом о новых данных и вообще часто на отдельный пин прерывания выносятся. Как еще вы предлагаете слэйву сообщить о том, что было что-то нажато, если обмен SPI может идти только при наличии клока от мастера?[/uquote]
Попробовал просто в прерывании по таймеру запускать считывание состояние, так тоже работает. Так будет удобнее, все равно будет какое-либо прерывание с таймером 1-10ms будет щелкать, там и буду запускать чтение.
Посыл понятен, что мега8, что вообще AVR - уже в возрасте, но что поделать, поздно начал.

Добавлено after 3 minutes 58 seconds:
[uquote="parovoZZ",url="/forum/viewtopic.php?p=3960098#p3960098"]

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

не МК,  а какая-то бедапечаль([/uquote]

Ну в данном случае и touch контроллер не фонтан, нету даже chip select, будет слушать весь clock, так как в предполагаемом устройстве есть еще пару устройств по spi, благо они в  одну сторону.
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Сообщение NStorm »

Посыл понятен, что мега8, что вообще AVR - уже в возрасте, но что поделать, поздно начал.
Atmega88 и 88А - почти всё тоже самое, но чуть больше всё-таки возможностей и удобнее. Если на потом затеяте что-то делать уже сразу хотя бы в их сторону посмотрите. Или сразу заранее подзакажите немного их или 168ых.
Встал на лапы
Аватара пользователя
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Сообщение sergo80zxc »

Все оказывается работает)))) одна 88 мега на выход перестала работать, заменил 328р)) всем спс)
глаза боятся, а руки что то не делают))
Встал на лапы
Аватара пользователя
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Сообщение sergo80zxc »

всем привет. захотел без делаев сделать программный спиай через прерывания таймера, но возник гемор как обычно)))) функция приема и отправки байта вроде как вполне рабочая на мой взгляд, загвоздка в том , что если ее в системный таймер оформляешь как быть с получением байта от слейва? так как в каждом тике таймера она отрабатывает как автомат постепенно с каждым тиком отсылая и принимая биты и в каждый тик таймера разное значение принятого байта выдает? весь байт она отправляет и принимает за 51 тик таймера


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

/*
 * GccApplication43.c
 *
 * Created: 20.01.2021 21:48:10
 *  Author: сергей
 */ 


// функцию вроде написал, как ее использовать не пойму байт передается за 51 вызов этой функции
//
//
//
// 
//
//     а по факту между прерываниями таймера получается 11000 тиков ядра МК
//

#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)


//-------------------------------------------------------- МАКРОС  ВКЛЮЧЕНИЯ И ОТКЛЮЧЕНИЯ ВЫВОДА (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 PIN_SPI   PINB                            // PINB  обзавем PIN_SPI  ( тут буит проверка бита и все таккое)))
#define PIN_MISO  PINB4                           // обзовем пин  (PINB4)    удобным именем (PIN_MISO)


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

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

//-----------------------------------------------------------------------------------------------------------------------------------------------------------
void TIMER0_init(void)                        // инициализация и настройка модуля таймера Т0 режим СТС прерывание и сброс по совпадению с содержимым регистра (OCR0A)
{
  TIMSK0 = 0b00000010;   // бит РВ1(OCIE0A) устанавливаем в 1, разрешения прерывания по совпадению блока А таймера /счетчика Т0
  TCCR0A = 0b00000010;   // биты (WGM02,WGM00) сбрасываем в 0, (WGM01) установим в 1 настройка ------- режима СТС
  TCCR0B = 0b00000010;   // настраиваем предделитель на 8 (биты  CS02=0,CS01=1,CS00=0),бит В3(WGM02) сбрасываем в 0
  OCR0A  = 100;          // запишем 100 в регистр сравнения (OCR0A) блока А
  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_COMPA_vect)                 // прерывание по совпадению блока А таймера  Т0
{				
	 sis_taimer ++;                 // инкрементируем наш системный таймер								
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t SPI_send_and_receive_bytes (uint8_t data)    // функция программного SPI. входные параметры байт для передачи (data), полученый байт возвращаем в вызывающюю функцию
{
  static uint8_t b = 7;                   // переменая (b) номер бита  байта (out_bytes) и байта (data). прием и передача байта начинаем с 7 старшего бита  ( настройка SPI 0)
  static uint8_t out_bytes = 0;           // создадим переменную (out_bytes), через нее  будем возвращать принятый байт в вызывающюю функцию	
  static uint8_t period_counter =0;       // счетчик периода SPI модуля 
  static uint8_t cycle_counter  =0;       // счетчик циклов SPI модуля   
  (period_counter) ++	;                 // инкрементируем наш счетчик
 			
  if (period_counter ==1)              // начало пириода  когда   SCK равно 0
  {	
	macro_START_SLAVE();               // старт ведомому МК ( активизируем ведомый МК  на прием и передачу данных сбросив в 0 пин и линию (SS))	
	PORT_SPI &= ~(1<<PB_SCK);          // пин (SCK) сбросим в 0,( режим ожидания )			
  }	

  //-------------------------отсюда буит работать цикл для 8 бит	

  if (period_counter ==2)              // середина пириода  когда SCK равно 0
  {
	if (data & (1 << b))               // проверяем  бит (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
	}
  }	
		
  if (period_counter ==3)              // начало пириода  когда   SCK равно 1 (передний фронт импульса и у нас считывание с MISO от СЛЕЙВА)
  {
	PORT_SPI |= (1<<PB_SCK);           // пин (SCK) установим в 1, нарастающий фронт)	  
	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) байта
  }	

  if (period_counter ==5)                      // конец пириода  когда   SCK равно 1 ( задний  фронт импульса)
  {
	PORT_SPI &= ~(1<<PB_SCK);                  // пин (SCK) сбросим в 0
  }

  if (period_counter ==6)                      // полпериода когда SCK равен 0
  {
    (cycle_counter) ++	;                      // инкрементируем наш счетчик циклов	 (прогнали полный цикл приема и отправки бита)	
    (period_counter ==1);                      // в счетчик периода ставим 1 и и при следующей интерации фу-ции пойдет прием и отправка следующего бита
  }
  
 //------------------------- конец цикла для 8 бит
	
  if (cycle_counter ==8)                       // отправили и приняли весь байт (все 8 бит)
  {
	 macro_STOP_SLAVE();                       // стоп ведомому МК	 	 
  }	
	 return (out_bytes);                // возвращаем значение полученого байта от слейва в вызывающюю функцию		
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
int main(void)
{
	DDR_LED  =  0b11111111;           // на выход ( на нем наши индикаторные светодиодики)))
	TIMER0_init ();                   // инициализация и настройка модуля таймера Т0
	SPI_MasterInit ();                // инициализация и настройка модуля SPI

	volatile  uint8_t input_byte;     // в эту переменную будем получать байт от СЛЕЙВА  из функции (SPI_send_and_receive_bytes)
	volatile  uint8_t output_byte;    // в этой переменной будет байт для отправки СЛЕЙВУ	
	
	sei();                            // Разрешить прерывания
	
	while (1)                         // Основной цикл программы
	{				

		
		
		if (sis_taimer ==1)                                       // системный таймер
		{
			input_byte = SPI_send_and_receive_bytes (output_byte); // присваиваем переменной (input_byte) возвращаемое значение функции (SPI_send_and_receive_bytes)
			sis_taimer =0;                                         // сбросим таймер и ждем следующего тика системного таймера
		}		
		
		
		
			
	    	asm("NOP");                                            // пустая операция
	}
}

глаза боятся, а руки что то не делают))
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Сообщение NStorm »

sergo80zxc, ну вы сами выбрали такой подход - систик и периодические действия. Подобный подход обычно реализуется во всяких ОСРВ, когда по времени нужно разделять различные псевдопарралельно выполняющиеся задачи. Отсюда и налицо накладные расходы в виде частого вызова. В ОСРВ тоже постоянные переключения между задачами (контекстами) вносят накладные расходы. Просто там, где задачи сложные, а "железо" достаточно мощное, ими можно пренебречь. Но чисто для одного SPI уж можно было и из прерывания вызывать функцию. Если что-то еще делать будете - хотя бы другие задачи не будут блокировать SPI и искажать клок.

Ну а так еще шифт битов в переменную делается проще. Вместо этого:

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

  if (period_counter ==3)              // начало пириода  когда   SCK равно 1 (передний фронт импульса и у нас считывание с MISO от СЛЕЙВА)
  {
   PORT_SPI |= (1<<PB_SCK);           // пин (SCK) установим в 1, нарастающий фронт)    
   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) байта
  }   
Делаем так:

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

  if (period_counter ==3)              // начало пириода  когда   SCK равно 1 (передний фронт импульса и у нас считывание с MISO от СЛЕЙВА)
  {
   PORT_SPI |= (1<<PB_SCK);           // пин (SCK) установим в 1, нарастающий фронт)    

   out_bytes |= (PIN_SPI & (1 << PIN_MISO)) << b; // нам всё-равно надо будет обнулить out_bytes после полного приема, поэтому если бит не 1, то 0 там будет и так
   b-- ;                              // декрементируем номер бита (b) байта
  }   

Далее:

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

  if (period_counter ==6)                      // полпериода когда SCK равен 0
  {
    (cycle_counter) ++   ;                      // инкрементируем наш счетчик циклов    (прогнали полный цикл приема и отправки бита)   
    (period_counter ==1);                      // в счетчик периода ставим 1 и и при следующей интерации фу-ции пойдет прием и отправка следующего бита
  }
== - ошибка и не 1, а в 0 надо ставить, потому в следующей итерации оно у вас сначала увеличивается и вы начнете сразу с 2.

Далее - нигде нет сброса значения b до 7. После первого байта оно станет у вас 255 и будет жопа. Аналогично нет сброса cycle_counter.

Нет нужды каждый вызов возвращать не до конца заполненное значение выходящего байта. И у вас нет "сигнала" о том, что принят весь байт. Как вы узнаете, что принят весь байт в итоге? Будете SS проверять разве что? А если следующий прием запустится к тому моменту? Возвращайте лучше true (1) когда принят весь байт и 0 в ином случае. А само значение байте в виде указателя на буфер в аргументе функции передавайте. Как-то так:

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

#include <stdbool.h>
...
bool SPI_send_and_receive_bytes (uint8_t data, uint8_t *out_bytes) {
...
   *out_bytes |= (PIN_SPI & (1 << PIN_MISO)) << b; // нам всё-равно надо будет обнулить out_bytes после полного приема, поэтому если бит не 1, то 0 там будет и так
...
  if (cycle_counter ==8)                       // отправили и приняли весь байт (все 8 бит)
  {
    macro_STOP_SLAVE();                       // стоп ведомому МК       
    b = 7; // не забываем сбросить b
    cycle_counter = 0; // и это
    return true; // байт принят, возвращаем true
  }   
    return false;                // байт еще не принят, возвращаем ложь
...
}
...

      if (sis_taimer ==1)                                       // системный таймер
      {
         if (SPI_send_and_receive_bytes(output_byte, &input_byte)) { // присваиваем переменной (input_byte) возвращаемое значение функции (SPI_send_and_receive_bytes)
            // вот теперь мы знаем, что приняли весь байт, тут что-то можно сделать...
            input_byte = 0; // и очистить буфер
         }
         sis_taimer =0;                                         // сбросим таймер и ждем следующего тика системного таймера
      }   
Ну и там всякие мелочи, вроде бесполезного NOP в конце бесконечного цикла. В нём нет смысла.

PS: Я может еще что проглядел, что надо исправить, не факт что всё увидел.
Встал на лапы
Аватара пользователя
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Сообщение sergo80zxc »

NStorm, спасибо) буду репу дальше чесать на счет сбросов согласен проглядел и будет переполнение, на счет (period_counter ==1); он и должен поравняться 1, чтобы продолжился цикл с 2 . это первый раз с 1 начинается цикл приема и отправки. да нопы тут пока не к чему))))) но у меня атмелстудия 6 глючит без этого нопа в симуляции))
по ходу функция приемопередачи байта на таймере выйдет тяжеловесной)))
глаза боятся, а руки что то не делают))
Встал на лапы
Аватара пользователя
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Сообщение sergo80zxc »

Здравствуйте товарищи коты)) выдался у меня выходной и я вновь погрузился в доработку кода) Написал, в железе все работает как надо))
Гемор вечный с протеусом , задумка была отправляю программным спиай слейву бегущий огонек, от слейва принимаю тоже бегущий огонек ( у слейва аппаратный спиай), так вот в протеусе на мастере постоянно горит 7 пин индикаторного порта, а в остальном все работает, огоньки бегут от 0 пина по 7 пин на обоих МК.
Вот и хотел спросить
в железе все путем на обоих мк огонек бежит от 0 по 7 пин циклически
в протеусе 7 пин на мастере загорается и горит все время, глюк протеуса или я криво кодил?
и 2 вопрос сильно некрасиво код пишу? если есть замечания поправьте а то в последствии хочу хобби в работу привратить)
и еще вопрос можно как то красивее программный спиай сделать? без указателей и глоальных переменных? но без делаев?

мастер с программным SPI
Спойлер

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

/*
 * GccApplication4.c          в железе работает как положено, в протеусе криво работает ( в МАСТЕРЕ постоянно горит 7 пин индикаторного порта PORTD7)
 *
 * Created: 08.02.2021 22:45:24
 * Author : Сергейда
 */ 

// -------ИЗУЧАЕМ ПРОГРАММНЫЙ SPI.-------------ДЛЯ МАСТЕРА-------------------
//
// организуем программный (SPI) на прерывании системного таймера ( дабы не останавливать все процессы в МК при отправке и приему байта)
// грубо говоря отправляем бегущий огонек СЛЕЙВУ, он нам тоже что то отправляет ( что принимаем от СЛЕЙВА выводим в порт индикации)
// 
//
//    
//

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#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)

//-------------------------------------------------------- МАКРОС  ВКЛЮЧЕНИЯ И ОТКЛЮЧЕНИЯ ВЫВОДА (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 PIN_SPI   PINB                            // PINB  обзавем PIN_SPI  ( тут буит проверка бита и все таккое)))
#define PIN_MISO  PINB4                           // обзовем пин  (PINB4)    удобным именем (PIN_MISO)
#define PIN_SS    PINB2                           // обзовем пин  (PINB2)    удобным именем (PIN_SS)

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

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

// ----------------------------------   ПЕРЕМЕННЫЕ (SPI) МОДУЛЯ   ----------------------------------------------------------------------------------------
     
volatile uint8_t send_byte = 0;      // объявим  переменную передаваемый байт модулю  (SPI)
volatile uint8_t byte_received = 0;  // объявим  переменную получаемый байт от модуля (SPI)
volatile uint8_t flag_SPI_module= 0; // (flag_SPI_module) индикатор работы (SPI) модуля. (flag_SPI_module=0) модуль в работе, (flag_SPI_module=1) работа завершена
// -------------------------------------------------------------------------------------------------------------------------------------------------------
   
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
void TIMER0_init(void)                        // инициализация и настройка модуля таймера Т0 режим СТС прерывание и сброс по совпадению с содержимым регистра (OCR0A)
{
  TIMSK0 = 0b00000010;   // бит РВ1(OCIE0A) устанавливаем в 1, разрешения прерывания по совпадению блока А таймера /счетчика Т0
  TCCR0A = 0b00000010;   // биты (WGM02,WGM00) сбрасываем в 0, (WGM01) установим в 1 настройка ------- режима СТС
  TCCR0B = 0b00000011;   // настраиваем предделитель на 64 (биты  CS02=0,CS01=1,CS00=1),бит В3(WGM02) сбрасываем в 0
  OCR0A  = 100;          // запишем 100 в регистр сравнения (OCR0A) блока А
  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_COMPA_vect)                 // прерывание по совпадению блока А таймера  Т0
{				
	 sis_taimer ++;                 // инкрементируем наш системный таймер								
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t running_light(void) // функция передает в вызывающюю функцию через return бегущий огонек (при каждом вызове в переданом байте еденица смещается на разряд влево)
{                           // примерно так- сначала 0b00000001, при следующем вызове 0b00000010, потом 0b00000100 и так по 0b10000000 и все заново с 0b00000001
	uint8_t data = 0;       // создаем и задаем значение  переменной (data),
	static uint8_t a = 0;     // создаем и задаем значение  статической переменной (a), она у нас будет номером бита в нашем байте данных (data)
	
	if (a>7) a=0;             // защита от переполнения, как только (а) станет =8 она обнулится , что бы не вылезти за размер байта
	data = (1 << a);          // установка еденицы в бит (а) байта (data) с затиранием остальных битов, например было (0b00000001) потом стало (0b00000010)
	a++ ; 	                  // инкрементируем наш номер бита в байте)))
	return (data);            // передаем в вызывающюю функцию наш байт
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
volatile uint8_t SPI_transceiver_module (volatile uint8_t *send_b, volatile uint8_t *b_received)    // функция программного SPI. входные параметры байт для передачи (data), полученый байт возвращаем в вызывающюю функцию
{
	static uint8_t b = 7;                   // переменая (b) номер бита  байта (out_bytes) и байта (data). прием и передача байта начинаем с 7 старшего бита  ( настройка SPI 0)
	static uint8_t period_counter =0;       // счетчик периода SPI модуля
	static uint8_t cycle_counter  =0;       // счетчик циклов SPI модуля
           uint8_t out_flag = 0;            // переменная (out_flag) индикатор работы модуля (SPI)	
	period_counter ++;                      // инкрементируем наш счетчик
	
	if (period_counter ==1)                 // начало периода  когда   SCK равно 0
	{
		macro_START_SLAVE();                // старт ведомому МК ( активизируем ведомый МК  на прием и передачу данных сбросив в 0 пин и линию (SS))
		PORT_SPI &= ~(1<<PB_SCK);           // пин (SCK) сбросим в 0,( режим ожидания )
	}
	//-------------------------отсюда буит работать цикл для 8 бит
	if (period_counter ==2)              // середина периода  когда SCK равно 0
	{
		if ((*send_b) & (1 << b))       // проверяем  бит (a) переменной (*send_byte), если этот бит = 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
		}
	}
	
	if (period_counter ==3)              // начало периода  когда   SCK равно 1 (передний фронт импульса и у нас считывание с MISO от СЛЕЙВА)
	{
		PORT_SPI |= (1<<PB_SCK);           // пин (SCK) установим в 1, нарастающий фронт)
		if (PIN_SPI & (1 << PIN_MISO))     // проверяем  бит (PIN_MISO) порта (PIN_SPI), если этот бит = 1, то выполняется КОД 1. (выражение в скобках)
		{   (*b_received) |= (1<<b);           // устанавливаем бит (b) байта (*byte_received) в 1.
		}
		else                               // иначе если бит (PB_MISO) = 0 , то выполняем КОД 2.
		{   (*b_received) &= ~(1<<b);          // сбрасываем пин (b)  байта (*byte_received) в 0.
		}
		b-- ;                              // декрементируем номер бита (b) байта
	}

	if (period_counter ==5)                      // конец периода  когда   SCK равно 1 ( задний  фронт импульса)
	{
		PORT_SPI &= ~(1<<PB_SCK);                  // пин (SCK) сбросим в 0
	}

	if (period_counter ==6)                      // пол периода когда SCK равен 0
	{
		cycle_counter ++;             // инкрементируем наш счетчик циклов	 (прогнали полный цикл приема и отправки бита)
		period_counter =1;            // присваиваем  счетчику периода  еденицу и  при следующей интерации фу-ции пойдет прием и отправка следующего бита с тика 2
	}
	
	//------------------------- конец цикла для 8 бит
	
	if (cycle_counter ==8)            // отправили и приняли весь байт (все 8 бит)
	{
		macro_STOP_SLAVE();           // стоп ведомому МК
		b = 7;                        // защита от переполнения
		cycle_counter = 0;            // защита от переполнения
		period_counter =0;            // присваиваем  счетчику периода ноль, что бы при работе со следующем байтом обнулился пин (SS) ( macro_START_SLAVE();)		
		out_flag = 1;                 // (out_flag)=1 (SPI) модуль передал байт СЛЕЙВУ и принял  байт от СЛЕЙВА (цикл приемопередачи (SPI) модуля завершон)
	}
	 return (out_flag);              // возвращаем значение	
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------

int main(void)
{	
	DDR_LED  =  0b11111111;              // на выход ( на нем наши индикаторные светодиодики)))
	TIMER0_init ();                      // инициализация и настройка модуля таймера Т0
	SPI_MasterInit ();                   // инициализация и настройка модуля SPI		
	sei();                               // Разрешить прерывания
	
	while (1)                            // Основной цикл программы
	{										
		if (sis_taimer == 1)                   // системный таймер
		{    
      	   if ((flag_SPI_module) == 1)         // ЕСЛИ переменная (flag_SPI_module) равна 1, то выполняется КОД.1   (передача и  прием байта закончены)
	       { 
		        PORT_LED = byte_received;      // КОД.1 принятый байт выведем в порт индикации
				send_byte = running_light ();  // в переменную (send_byte) на отправку слейву	загоняем бегущий огонек из ф-ии (running_light)				
		        flag_SPI_module = 0;           // сбрасываем (flag_SPI_module) в 0.			  			  			  						  
	       }
		   			
           else                                // ИНАЧЕ переменная (flag_SPI_module) равна 0, то выполняется КОД.2  ((SPI) модуль работает, передает и принимает)
	       {
		        flag_SPI_module = SPI_transceiver_module (&send_byte, &byte_received);   // КОД.2 вызываем функцию (SPI_transceiver_module) и передаем в
	       }		                                                                     // переменную (flag_SPI_module) состояние (SPI) модуля		      							  			  						  				  		  			        			  		  		        	  
	        sis_taimer =0;                                         // сбросим таймер и ждем следующего тика системного таймера		  	
        }				
	    	asm("NOP");                                            // пустая операция
	 }
}

СЛЕЙВ аппаратный SPI

Спойлер

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

/*
 * 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");                       // пустая операция
	}
}
глаза боятся, а руки что то не делают))
Опытный кот
Сообщения: 859
Зарегистрирован: Пт янв 27, 2012 20:07:54
Откуда: Томск

Сообщение pavel2000 »

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

uint8_t running_light(void) // функция передает в вызывающюю функцию через return бегущий огонек (при каждом вызове в переданом байте еденица смещается на разряд влево)
{                           // примерно так- сначала 0b00000001, при следующем вызове 0b00000010, потом 0b00000100 и так по 0b10000000 и все заново с 0b00000001
   uint8_t data = 0;       // создаем и задаем значение  переменной (data),
   static uint8_t a = 0;     // создаем и задаем значение  статической переменной (a), она у нас будет номером бита в нашем байте данных (data)
   
   if (a>7) a=0;             // защита от переполнения, как только (а) станет =8 она обнулится , что бы не вылезти за размер байта
   data = (1 << a);          // установка еденицы в бит (а) байта (data) с затиранием остальных битов, например было (0b00000001) потом стало (0b00000010)
   a++ ;                      // инкрементируем наш номер бита в байте)))
   return (data);            // передаем в вызывающюю функцию наш байт
}
Вот это можно оптимизировать как-то так

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

 
     static uint8_t t = 0;
      t = t << 1;  //t = (uint16_t)(t << 1) & 0xFF;
      if (t == 0)
        t = 1;
      return t;
Это устранит цикл, который появляется от реализации "data = (1 << a);", т.к. процессор может сдвинуть только на один бит за операцию, а также уменьшит размер кода.
В вашем случае конечно такая оптимизация не требуется, приведено для ознакомления.
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Сообщение NStorm »

Не будет там цикла, если включить хоть какую-нибудь оптимизацию. Компилятор не такой тупой.
Опытный кот
Сообщения: 859
Зарегистрирован: Пт янв 27, 2012 20:07:54
Откуда: Томск

Сообщение pavel2000 »

Позвольте поинтересоваться, какими же инструкциями AVR архитектуры можно сдвинуть содержимое переменной (регистра/регистров) на значение, хранящееся в другом регистре, без использования цикла?

Спасибо.
Ответить

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