#include //Библиотека ввода/вывода #include //Библиотека прерываний #include // можно попробовать включить подавление шумов в счётчике, и посмотреть, что будет. По идее, должно работать, если ОУ и оптрон не будут тормозить. // #define NO_NOISE_IN_COUNTER /* распиновка: PD0, PD1 - RS232 PD2 - кнопка "сменить кристалл", 1- вкл PD3 - реле "крутить угол вверх", 1- вкл PD4 - реле "крутить угол вниз", 1- вкл PD5 - счётчик фотонов PD6 - импульсы на двигатель. Лампочки состояния кристаллов, 1 - не горит: PB0 PB1 PB2 PB3 Протокол обмена 9600 БОД, 8 бит, нет чётности, 1 стоп. Данные обрабатываются между получением их микроконтроллерами и отправкой ответа Инициируется ПК RX Байт команд 0 0 - ок, 1 - запрос повтора предыдущей передачи 1 0 - передача в первый раз, 1 - повтор 2 1 - сменить кристалл 3 1- включить реле вверх 4 1- включить реле вниз 5 0 spare 6 Contorl Sum 7 Contorl Sum Два байта, период шага мотора в мс (меандр 50%) сначала старший, потом младший. Tx Байт команд Биты 3,4,5 совпадают по расположению с командными битами PD, так что их можно тупо копировать в регистр 0 0 - ок, 1 - запрос повтора предыдущей передачи 1 0 - передача в первый раз, 1 - повтор 2 1 - смена кристалла, 0 - кристалл установлен 3 3, 4 - два бита номера кристаллов 4 3, 4 - два бита номера кристаллов 5 0 OK, 1 - ошибка прибора 6 Contorl Sum 7 Contorl Sum 3 байта подсчитанных фотонов, старший - средний - младший 2 байта количество переданных шагов двигателя, сначала старший, потом младший. 2 байта количество милисекунд, прошедших с формирования микроконтроллером предыдущего пакета, сначала старший, потом младший. Итого - нормальный пакет - 3 байта в контроллер, 8 байтов наружу. CRC подсчитывается как последовательный xor всех байтов количества и байта команд (без последних двух бит, которые CRC), полученный байт сначала ксорится по тетрадам, а потом - по диадам полученной тетрады. Если какая-то сторона запрашивает повтор, в ответ высылается копия предыдущего сообщения с выставленным битом повтора, остальные биты пакета при этом игнорируются (кроме CS). Обычно после этого сразу же выполняется следующий раунд обмена данными. */ //магические команды: //количество импульсов на один градус // анализ пришедшего байта команд #define isnotOk(x) ((x)&1) #define isRepead(x) ((x)&2) #define getCMD(x) ((x)&(4+8+16)) #define getCS(x) (((x)&(64+128))>>6) // формирование уходящего байта команд #define setnotOk(x) x|=1; #define setRepead(x) x|=2; #define setInChange(x) x|=4; #define setCrystal(x,y) {x&=~(8+16); x|=(((y)&3)<<3);} #define setError(x) x|=32; #define clearCS(x) (x&(~(64+128))) #define setCS(x,y) {x=clearCS(x); x|=(((y)&3)<<6);} #define blink_drive() PORTD^=64 unsigned char rxbuf[3], txbuf[8], num_rx=0, num_tx=0;//буферы UART и количество байт, которые уже были получены. unsigned char T1_overflows=0, T0_count=0; // T1_overflows - счётчик переполнений, превращающий T1 в 24-битный регистр. T0_count - счётчик переполнений, отсчитывающий время работы реле #define T0_count_bas 20 //количество переполнений, совершаемых счётчиком 0 перед сбросом реле. Один тик ~ 16 мс. #define half_ms 125 //при частоте 16 МГц и делителе 64 столько шагов таймера приходится на 1/2 мс unsigned int half_ms_counter=0, half_ms_period=0, drive_counter=0;// переменные двигателя - сколько прошло половин милисекунд, сколько половин милисекунд в половине требуемого периода, сколько отсчётов двигателя сделано. // если half_ms_period равен нулю, двигетель стоит unsigned int half_ms_from_sending=0;// количество полумилисекунд, прошедших с формирования предыдущего пакета int main (void) { unsigned char countCS(unsigned char* buf, unsigned char lng); cli();// запретить прерывания // настроить направления портов ввода-вывода PORTD=0; DDRD=4+8+16; DDRB=0; PORTB=1+2+4+8; // настроить счётчик т0 (для сброса нажатий кнопок) TCCR0B=5;// прескалер на 1024 TIMSK0=1;//разрешаем прерывание // настроить счётчик т2 (милисекундный таймер) TCCR2B=4;// прескалер на 64 TIMSK2=2;//разрешаем прерывание по совпадению с регистром А OCR2A=half_ms;//Записываем в этот регистр количество тиков, нужное для 1/2 ms // настроить счётчик т1 TCCR1B=(1<<6)+1; //По ноге, по нисходящему сигналу TIMSK1=1;//разрешаем прерывание #ifdef NO_NOISE_IN_COUNTER TCCR1B|=(1<<6); //Надо включить фильтрацию шумов, и посмотреть, что будет. #endif // настроить UART UCSR0B=8+16;// разрешает передачу и получение. UBRR0L=103; //9600 БОД; 16000000/(16*9600)-1 = 103.1666666667 sei();// разрешить прерывания while(1) { while(!(UCSR0A&128)){};//ждём, пока придёт байт. rxbuf[0]=UDR0; while(!(UCSR0A&128)){};//ждём, пока придёт байт. rxbuf[1]=UDR0; while(!(UCSR0A&128)){};//ждём, пока придёт байт. rxbuf[2]=UDR0; //тут обрабатываем rxbuf и формируем txbuf if (countCS(rxbuf,3)==getCS(rxbuf[0])) // сравниваем расчётную контрольную сумму с двумя полученной. { if(isnotOk(rxbuf[0])) // запросили отправку копии? { setRepead(txbuf[0]); setCS(txbuf[0],countCS(txbuf,8)); } else // ура, можно обрабатывать пакет и высылать актуальные данные { //начинаем отсчёт перед сбросом T0_count=T0_count_bas; PORTD|=getCMD(rxbuf[0]);//копируем в регистр пришедшие команды if (!half_ms_counter)half_ms_counter=(((unsigned int)rxbuf[1])<<8)+rxbuf[2];// если счётчик на нуле, его нужно инициализировать. Если нет, то пусть тикает как тикал. half_ms_period=(((unsigned int)rxbuf[1])<<8)+rxbuf[2]; if (!half_ms_counter)half_ms_counter=half_ms_period;// если счётчик на нуле, его нужно инициализировать. Если нет, то пусть тикает как тикал. if (!half_ms_period)half_ms_counter=0; // корректно останавливаем двигатель. txbuf[0]=0; switch (PINB&(1+2+4+8)) { case 0xf: setInChange(txbuf[0]); break; case 0xe: setCrystal(txbuf[0],0); break; case 0xd: setCrystal(txbuf[0],1); break; case 0xb: setCrystal(txbuf[0],2); break; case 0x7: setCrystal(txbuf[0],3); break; default: setError(txbuf[0]); break; } cli(); // Считываем значение счётчика, и обнуляем его. txbuf[3]=TCNT1L; txbuf[2]=TCNT1H; txbuf[1]=T1_overflows; TCNT1H=0; TCNT1L=0; T1_overflows=0; // записываем количество сделанных шагов, и обнуляем его. txbuf[5]=drive_counter&0xff; txbuf[4]=(drive_counter&0xff00)>>8; drive_counter=0; // записываем количество прошедших милисекунд; half_ms_from_sending>>=1;// превращаем полумилисекунды в милисекунды txbuf[6]=half_ms_from_sending&0xff; txbuf[7]=(half_ms_from_sending&0xff00)>>8; sei(); setCS(txbuf[0],countCS(txbuf,8)); } } else // ошибка получения, запрашиваем копию { setnotOk(txbuf[0]); setCS(txbuf[0],countCS(txbuf,8)); } while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. Ну, по идее, он и так ушёл, но ладно. UDR0=txbuf[0]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[1]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[2]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[3]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[4]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[5]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[6]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. UDR0=txbuf[7]; while(!(UCSR0A&64)){};//ждём, пока уйдёт байт. } return 0; } // при переполнении т1 увеличить счётчик переполнений ISR(TIMER1_OVF_vect) { T1_overflows++; } // при прохождении 1/2 ms по таймеру 2 - увеличить количество прошедших милисекунд, и тикнуть двигателем, если надо. ISR(TIMER2_COMPA_vect) { TCNT2=0; half_ms_from_sending++; if(half_ms_period)// если двигатель не стоит { half_ms_counter--; if (!half_ms_counter) { half_ms_counter=half_ms_period; blink_drive(); drive_counter++; } } } // при переполнении т0 сбросить команды ISR(TIMER0_OVF_vect) { if(T0_count) { T0_count--; if(!T0_count) PORTD&=~getCMD(0xff); } } unsigned char countCS(unsigned char* buf, unsigned char lng) { unsigned char i, cs=clearCS(buf[0]);//первый байт, за исключением двух старших бит, в которых и находится контрольная сумма. for(i=1;i>4); // старшая тетрада xor младшая тетрада cs=(cs&3)^((cs&12)>>2); // то же с диадами return cs; }