вторник, 14 сентября 2010 г.

Пример прошивки для ATMega8

#include <mega8.h>
#include <stdio.h> // Standard Input/Output functions
#include <delay.h>
#include "tpalibs.h"
#include "modlibs.h"
#include "bits.h"

// Здесь устанавливается версия программы, которая потом будет отдаваться по MDB по команде CONFIG
#define cMajorVersion 0x01                // Мажор и минор версии этой программы
#define cMinorVersion 0x04                // Version = "cMajorVersion.cMinorVersion"
// Описание функций
byte MotorInit(void);                    // Инициализация замка
byte MotorControl(byte);                // Управление мотором LOCKOPEN, LOCKCLOSE, LOCKBRAK, LOCKSTOP
#ifdef MDB_MODE
void MDBInit(void);
void MDBConfig(void);
void MDBDiagnostic(void);
void MDBPoll(void);
void MDBEvent(void);
#else
void ADCReadAndPrint(void);
#endif
// Описание внешних переменных
#ifdef MDB_MODE
byte LastEvent = 0;
#endif
// Начало программы
void main(void) {
    byte status;
    byte inbyte;
    byte MotorMode;                        // Статус мотора
    word W;                                // Значение АЦП
    bit isCanOpenDoor = 1, isCanCloseDoor = 1;        // 2 взаимосвязанных битовых флага регистра замка
    bit isGerkonOpen = 1, isGerkonClose = 1;        // 2 взаимосвязанных битовых флага регистра геркона двери
    // bit ENABLEPRINTW;                    // Печатать ли ADC
    #ifdef MDB_MODE
    bit MDBBUSResetSignal = 0;            // Сигнал на Reset контроллера
    SelfAddress = cSelfAddress;
    #endif
    blinking();
    #ifdef MDB_MODE
    init_mega8(SERIAL9);            // Инициализируем кристалл в 9-ти битным UART'ом
    #else
    init_mega8(SERIAL8);            // Инициализируем кристалл в 8-ми битным UART'ом
    #endif
    blinking();
    #ifndef MDB_MODE
    printf("\n\rСтарт тестового ПО замка ИК барьера. Версия %u.%u(С). ", cMajorVersion, cMinorVersion);
    printf("Параметры прошивки: 4.1 мс + 6 СК (Внутренни RC - 4 МГц)\n\r");
    #else
    ReturnFromCommand();
    #endif
    blinking();
    #asm("sei")                        // Global enable interrupts
    MotorInit();
    while (1) {
        if ((status=UCSRA) & RX_COMPLETE) {    // Если что-то есть в порту
            #ifdef MDB_MODE
            recv_vmc(SelfAddress);
            #else
            inbyte = MDBPort;
            switch (inbyte) {
                case 0x31:
                    MOTORPHASE = 1; printf("PHASE = 1\r\n"); break;
                case 0x32:
                    MOTORSLEEP = 1; printf("SLEEP = 1\r\n"); break;
                case 0x33:
                    MOTORENABLE = 1; printf("ENABLE = 1\r\n"); break;
                case 0x34:
                    MOTORPHASE = 0; printf("PHASE = 0\r\n"); break;
                case 0x35:
                    MOTORSLEEP = 0; printf("SLEEP = 0\r\n"); break;
                case 0x36:
                    MOTORENABLE = 0; printf("ENABLE = 0\r\n"); break;
                case 0x7A:          // 'z'
                    MotorInit(); break;
                case 0x78:          // 'x'
                    printf("Открываем замок\r\n"); MotorControl(LOCKOPEN); break;
                case 0x63:            // 'c'
                    printf("Закрываем замок\r\n"); MotorControl(LOCKCLOSE); break;
                case 0x37:            // Зажигаем на время светик на плате
                    ERRLAMP = 1; Timer0Counter = cTimer0Counter; break;
                case 0x38:            // Отключаем принудительно светик на плате
                    ERRLAMP = 0; break;
                case 0x39:            // Печатаем значение всех ADC
                    ADCReadAndPrint(); break;
                default:
                    MotorControl(0);
                    printf("СТОП мотор! UDR = %x ", inbyte); break;
            };
            #endif
        };
        #ifdef MDB_MODE
        if (ComandCompleted) {        // Если прием от VMC завершился
            // Если сейчас режим ожидания подтверждения от VMC
            if (ConfirmFromVMC) confirm_vmc(); else {
                switch (CommandFromVMC) {
                    case 0 :
                        MDBInit(); break;
                    case 1 :
                        MDBConfig(); break;
                    case 2 :
                        MDBDiagnostic(); break;
                    case 3 :
                        MDBPoll(); break;
                    case 4 :
                        MDBEvent(); break;
                    default :
                        ReturnFromCommand(); break;
                };
            };
        };
        /*     Здесь определяем реакцию программы на ресет по шине MDB
            Анализ ресета по шине длиной 522 мс при частоте 8 MHz
            (Внимание! Если меняется частота, пересчитать константу cResetTimer) */
        if (RXD_MDB) MDBBUSResetSignal = 0; else { // если на шине установлена логическая 1, то игнорируем дальнейший код
            // Как только на шине выставился логический 0, то включаем подсчет времени
            if (!MDBBUSResetSignal) { Timer2Counter = 0; MDBBUSResetSignal = 1; };
            // Если кол-во срабатываний таймера превысило 16, то пора перегружаться
            if (Timer2Counter >= TimeToRestart) {
                #asm("RJMP __RESET");
            };
        };
        #endif
        // Проверяем значение датчиков на моторе замка
        if (DATAKEY1) {
            BLINKLAMP = 1; Timer1Counter = cTimer1Counter;
            if (isCanOpenDoor) {    // Здесь код для 1-го срабатывания
                isCanOpenDoor = 0; isCanCloseDoor = 1;
                MotorControl(LOCKSTOP); LastEvent = evLOCKCLOSE;
            };
        } else BLINKLAMP = 0;
        if (DATAKEY2) {
            ERRLAMP = 1; Timer1Counter = cTimer1Counter;
            if (isCanCloseDoor) {    // Здесь код для 2-го срабатывания
                isCanCloseDoor = 0; isCanOpenDoor = 1;
                MotorControl(LOCKSTOP); LastEvent = evLOCKOPEN;
            };
        } else ERRLAMP = 0;
        // Если мотор сейчас крутится, то анализируем потребление тока
        if (MOTORTURNONMODE) {            // Анализируем статус мотора (если не 0, то начинаем анализ)
            W = read_adc(cDC_Motor);    // Измеряем 2-й канал, если мотор уже крутится
            if (Timer0Counter == 0) {        // Если уже можно мерять, то выполняем нижеследующую часть кода
                // printf ("TimerCount = %d ", Timer1Counter);
                if (W > cMinADCMotor) {
                    if (W > cMaxADCMotor) {
                        #ifdef MDBMODE
                        LastEvent = evDIAGFALSE;
                        #else
                        printf("Мотор перегружен, W = %d... Остановите его кто-нибудь! ", W);
                        #endif
                        MotorControl(LOCKSTOP);
                    };
                } else {
                    // Мотор отключен или сломан. Подаем команду остановки на кристалл
                    #ifdef MDBMODE
                    LastEvent = evDIAGFALSE;
                    #else
                    printf("Мотор отключен или сломан. W = %d ", W);
                    #endif
                    // delay_ms(500);
                };
            };
        };
        // Проверяем геркон двери
        if (GERKONMODE) {
            if (isGerkonOpen) { LastEvent = evGERKONOPEN; isGerkonOpen = 0; isGerkonClose = 1; };
        } else {
            if (isGerkonClose) { LastEvent = evGERKONCLOSE; isGerkonOpen = 1; isGerkonClose = 0; };
        };
    };
}
#ifdef MDB_MODE
// INIT
void MDBInit(void) {
    // Если контрольная сумма 0, то отсылаем ACK
    if (~ControlSumError) send_vmc(cACK, 1); else send_vmc(cNAK, 1);
    MotorInit();                    // взводим мотор в исходное состояние
    ReturnFromCommand();            // взводим в исходное состояние
};
// CONFIG
void MDBConfig(void) {
    // Если ошибка в контрольной сумме - посылаем NACK
    if (ControlSumError) { send_vmc(cNAK, 1); return; };
    // Сначала посылаем мажор и минор версии этой программы (2 байта)
    byte_vmc(cMajorVersion); byte_vmc(cMinorVersion);
    // Отсылаем контрольную сумму
    crc_vmc();
    ConfirmFromVMC = 1;             // Включаем ожидание от VMC
};
// DIAG
void MDBDiagnostic(void) {
    // Если контрольная сумма 0, то отсылаем ACK
    if (~ControlSumError) send_vmc(cACK, 1); else send_vmc(cNAK, 1);
    ReturnFromCommand();
};
// POLL
void MDBPoll(void) {
    // Если контрольная сумма 0, то отсылаем ACK
    if (~ControlSumError) send_vmc(cACK, 1); else send_vmc(cNAK, 1);
    ReturnFromCommand();    // взводим в исходное состояние
};
// EVENT
void MDBEvent(void) {
    byte inbyte, mstatus;
    // Анализируем событие во втором байте.
    inbyte = FromVMC[1];
    switch (inbyte) {
        case 0x00:                     // Представляем значение ИК-барьера после обработки
            byte_vmc(LastEvent); LastEvent = 0;
            break;
        case 0xA0:
            mstatus = MotorControl(LOCKOPEN);
            byte_vmc(mstatus);
            break;
        case 0xA8:
            mstatus = MotorControl(LOCKCLOSE);
            byte_vmc(mstatus);
            break;
        default:
            Timer1Counter = cTimer1Counter; ERRLAMP = 1;         // Зажигаем светик ошибки  SETBIT(ERRPORT,ERRPIN);
            byte_vmc(0);
            break;
    };
    byte_vmc(SelfAddress);
    crc_vmc();
    ConfirmFromVMC = 1;             // Включаем ожидание от VMC
};
#else
// Печать всех входов АЦП
void ADCReadAndPrint(void) {
    byte i;
    word ADCResult;
    printf("Старт измерений по всем каналам\n\r");
    for (i = 0; i < cADCChannels; i++) {
        ADCResult = read_adc(i);
        printf("Канал %u, значение %u\n\r", i, ADCResult);
        delay_ms(50);
    };
};
#endif
// Инициализация замка
byte MotorInit(void) {
    #ifndef MDB_MODE
    printf("Инициализация замка\r\n");
    #endif
    if ((!DATAKEY2) && (!DATAKEY1)) MotorControl(LOCKCLOSE);
    if (DATAKEY2 && (!DATAKEY1)) return (0);
    delay_ms(1000);
    MotorControl(LOCKCLOSE);
    #ifndef MDB_MODE
    printf("Вторая попытка\r\n");
    #endif
    if (DATAKEY2 && (!DATAKEY1)) return (0);
    delay_ms(1000);
    MotorControl(LOCKCLOSE);
    #ifndef MDB_MODE
    printf("Не удалось\r\n");
    #endif
    return (0);
};
// Управление мотором LOCKOPEN, LOCKCLOSE, LOCKBRAK, LOCKSTOP
byte MotorControl(byte MotorComand) {
    byte MotorMode;                    // Локальная переменная текущего состояния мотора
    MotorMode = MOTORSTATUS;        // Вычитываем 3 бита состояния
    if (MotorMode == MotorComand) return (MotorMode); // Если мотор в этом же режиме, то сразу выходим
    // printf(" Команда замку = %x ", MotorComand);
    switch (MotorComand) {
        case LOCKSTOP:
            MOTORENABLE = 0; MOTORSLEEP = 0; MOTORPHASE = 0;
            break;
        case LOCKOPEN:
            MOTORENABLE = 1; MOTORSLEEP = 1; MOTORPHASE = 0;
            break;
        case LOCKCLOSE:
            MOTORENABLE = 1; MOTORSLEEP = 1; MOTORPHASE = 1;
            break;
        case LOCKBRAK:
            MOTORENABLE = 0; MOTORSLEEP = 1; MotorMode = MotorControl(LOCKSTOP);
            break;
        default:
            MotorMode = MotorControl(LOCKSTOP);
            break;
    };
    Timer0Counter = cTimer0Counter;
    return (MOTORSTATUS);
};
// Сработка KEY1
// interrupt [EXT_INT0] void ext_int0_isr(void) {        // External Interrupt 0 service routine
// };
// Сработка KEY2
// interrupt [EXT_INT1] void ext_int1_isr(void) {        // External Interrupt 0 service routine
// };

пятница, 20 августа 2010 г.

Пример тестирования контроллера лотка (ATMega8)

$regfile = "m8def.dat"
$include "Shablon-m8.bas"
' Описание конфигурации
Config Com1 = 9600 , Synchrone = 0 , Parity = None , Stopbits = 1 , Databits = 9 , Clockpol = 0
' Описание Aliases
' Описание констант
Const cMajorVersion = &H1                                   ' Мажор и минор версии этой программы
Const cMinorVersion = &H1E                                  ' Version = "cMajorVersion.cMinorVersion"
Const cSetupTimer = 15                                      ' Сколько миллисекунд ждать, чтобы начать анализ состояния при конфигурации
Const cTimerMotor = 7                                       ' Сколько миллисекунд ждать, чтобы начать анализ состояния
' Описание процедур
Declare Sub StartMotor(ByVal MotorNum As Byte)              ' Процедура старта мотора
Declare Sub StopMotor(ByVal MotorNum As Byte)               ' Процедура стоп мотор
' Описание переменных
Dim I As Byte                                               ' Счетчик циклов
Dim AllSW As Byte                                           ' 6 ног порта B
'Dim CountRx As Byte                                         ' Счетчик приемных байтов
' Dim Lighter As Bit                                          ' Моргающий индикатор
Dim SpiralNumber As Byte                                    ' Номер спирали для прокрутки
' Признак того, что на прерывании во время ожидания подтверждения байт пришел
Dim ComandCompleted As Bit
Dim MotorCom As Byte                                        ' Байт команд моторам
Dim MotorMod As Byte                                        ' Байт моторов (1 - вращение., 0 - выкл.)
Dim MotorClick As Byte                                      ' Байт срабатывания датчиков
Dim Motor5 As Byte                                          ' Состояние 5-го сектора вращения мотора
Dim MotorSel As Byte                                        ' Байт выбора текущего мотора
Dim MotorExist As Byte                                      ' Байт наличия живых моторов (Если бит сброшен, то мотор отсутствует или поломан
Dim LighterErr As Bit                                       ' Красный индикатор ошибки, который тушится после 3-х срабатываний таймера
Dim Res As Byte                                             ' Результат извлечения
Dim TimeErr As Byte                                         ' Счетчик срабатываний таймер для засветки ошибки
Dim CountInt As Word                                        ' Счетчик срабатывания датчика
Dim lOutByte As Byte
' Инициализация переменных из EEPROM
' ReadEEProm SelfAddress , SelfAddressDefault
' ReadEEProm SpiralCount , SelfSpiralCount
' Инициализация переменных
GoSub InitEnv
' Проверяем наличие моторов
For Res = 0 To 6
   ' Включаем на 2 ms мотор
   Set MotorPort.Res : Waitms cSetupTimer
   ' Если на входе стоит логическая ебиница, то мотор существует и исправен
   MotorExist.Res = MotorSignal
   ' Останавливаем мотор
   Reset MotorPort.Res : Waitms cSetupTimer
Next
' Проверяем адрес, установленный переключателем
I = AllSW AND &B111000 : SelfAddress = SelfAddress + I
' Проверяем кол-во спиралей, установленных переключателем
I = PINB AND &B00011 : SpiralCount = SpiralCount + I
' Инициализация прерываний
On Timer0 Timer0Sub
On Timer1 Timer1Sub
On INT1 ToggleMotor
Enable INTERRUPTS
Enable Timer0
Enable Timer1
Enable INT1
' Устанавливаем режим режим мультипроцессорного обмена (РМО)
Call ReturnFromCommand
'Основной цикл
Do
  ' Если что-то есть в приемном буфере, то начинаем его чтение
  If isPortReceived = 1 Then GoSub RECEIVEMDB
  ' Прием от VMC
  If ComandCompleted = 1 Then
     If ConfirmFromVMC = 0 Then
        Select Case CommandFromVMC
           Case 0 : GoSub MDBInit
           Case 1 : GoSub MDBConfig
           Case 2 : GoSub MDBDiagnostic
           Case 3 : GoSub MDBPoll
           Case 4 : GoSub MDBEvent
           Case Else
               ' Надо вернуться в исходное состояние
               Call ReturnFromCommand
           End Select
     Else
        GoSub VMCConfirm
     End If
  End If
  ' Анализируем состояние мотора
  If MotorStatus = 0 Then
     ' Если сейчас мотор не крутится
     If MotorCom > 0 Then
        MotorSel = MotorCom - 1
        ' Обнуляем команду
        ' Print "UDR = " ; Ina ; "; MotorSel = " ; MotorSel ; "; MotorCom = " ; MotorCom ; "; PINB = " ; PINB
        ' Print "==="
        MotorCom = 0
        ' Стартуем мотор
        If MotorExist.MotorSel = 1 Then Call StartMotor(MotorSel)
     End If
  Else
     ' Если крутится, то проверяется ошибка команды
     If MotorCom > 0 Then
        MotorCom = 0                                        ' Обнуляем команду
        TimeErr = cTimeErr                                  ': Set LighterErr                 ' Включаем ошибку
     End If
     ' Собственно здесь и ловим срабатывание датчика
     ' If MotorSignal = 0 Then GoSub TOGGLEMOTOR
  End If
  ' Анализ ресета по шине длиной 480 мс при 4 MHz (Внимание! Если меняется частота, пересчитать константу cResetTimer)
  ' If RXD_MDB = 0 Then
  '  If MDBBUSResetSignal = 0 Then : Timer1 = 0 : Set MDBBUSResetSignal : End If
  '   WordTMP = TIMER1
  '   If WordTMP > cResetTimer Then Return                   ' Перезагрузка
  'Else
  '   Reset MDBBUSResetSignal
  'End If
Loop
'Подпрограммы обработки команд
' (c)
MDBInit:
   If ControlSumError = 0 Then Call SendMDB(cACK , 1) Else Call SendMDB(cNAK , 1)
   ' Ищем и читаем ID термометра
   RegNumber(1) = 1wsearchfirst()
   Call ReturnFromCommand
Return
' (c)
MDBConfig:
   ' Сначала посылаем мажор и минор версии этой программы (2 байта)
   Call Byte2MDB(cMajorVersion) : Call Byte2MDB(cMinorVersion)
   ' Посылаем кол-во спиралей прошитых и обнаруженных
   Call Byte2MDB(SpiralCount) : Call Byte2MDB(MotorExist)
   ' Отсылаем ID термометра VMC
   GoSub SendRegNumber
   ' Отсылаем контрольную сумму
   Call CRC2MDB
   ' Включаем ожидание от VMC
   Set ConfirmFromVMC
Return
' (c)
MDBDiagnostic:
   If ControlSumError = 0 Then Call SendMDB(cACK , 1) Else Call SendMDB(cNAK , 1)
   Call ReturnFromCommand
   ' Даем команду на измерение температуры (convert)
   GoSub Reset1Wire
   1wwrite &H44
   Wait 1
   GoSub Reset1Wire
   1wwrite &HBE
   ' Читаем ее и заполняем массив, который отправится при следующем EVENT
   RegAnswer(1) = 1wread(9)
   1wreset
Return
' (c)
MDBPoll:
   If ControlSumError = 0 Then Call SendMDB(cACK , 1) Else Call SendMDB(cNAK , 1)
   Call ReturnFromCommand
Return
' (c)
MDBEvent:
   ' Анализируем событие во втором байте. Если событие в диапазоне &H11..&H17, значит крутим спираль
   InA = FromVMC(2) AND &H30
   ' Если спираль сейчас крутится, то игнорируем команду
   If MotorStatus <> 0 Then Ina = &HF0
   Select Case InA
      Case &H10
         ' Выделяем адрес спирали. Если спираль указана правильно, то уменьшаем ее номер, так как ноги считаются от 0
         SpiralNumber = FromVMC(2) AND &B111                ' : If SpiralNumber > 0 Then Decr SpiralNumber
         ' Запускаем на обработку команды
         MotorCom = SpiralNumber
         Call Byte2MDB(MotorCom)
      Case &H20
         ' Отсылаем заранее подготовленный массив RegNumber
         GoSub SendRegAnswer
      Case Else
         Call Byte2MDB(0)
   End Select
   Call Byte2MDB(SelfAddress)
   Call CRC2MDB()
   Set ConfirmFromVMC
Return
' Процедура отсылки RegNumber(8) VMC
SendRegNumber:
   For I = 1 to 8 : Call Byte2MDB(RegNumber(I)) : Next
Return
' Процедура отсылки RegAnswer(9) VMC. После отсылки очищаем массив
SendRegAnswer:
   For I = 1 to 9
       Call Byte2MDB(RegAnswer(I)) : RegAnswer(I) = &HFF
   Next
Return
' Процедура ожидания подтверждения от VMC
' (c) В С++ эта процедура реализована как confirm_vmc
VMCConfirm:
   Reset ComandCompleted
   Select Case FromVMC(nFromVMC)
      Case cACK
         Call ReturnFromCommand : Reset ConfirmFromVMC
      Case cNAK
         Call ReturnFromCommand : Reset ConfirmFromVMC
      Case cRET
         Set ConfirmFromVMC
   End Select
Return
'Подпрограмма обработки Timer0 (c)
Timer0Sub:
   If Timer0Counter <> 0 Then                               ' Ждем, пока Timer0Counter станет равным нулю
      Decr Timer0Counter
   Else
      If BlinkLamp <> 0 Then Reset BlinkLamp
   End If
Return
'Подпрограмма обработки Timer1 (c)
Timer1Sub:
   ' Toggle Lighter                                           ' Print "Timer1: " ;
   ' Проверяем, крутиться ли мотор
   If MotorStatus <> 0 Then Incr Motor5                     ' Еслит крутиться, то прибавляем счетчик сработанного таймера
   ' Если сейчас светик ошибки не светится, то выходим из прерывания
   If LighterErr <> 0 Then
       ' Ждем, пока TimeErr станет равным нулю
       If TimeErr = 0 Then Reset LighterErr Else Decr TimeErr
   End If
   ' Читаем значение ADC
   'Print "ch0 = " ; Getadc(0) ; "; ch1 = " ; Getadc(1)
Return
'Подпрограмма ресета и поиска устройста 1wire
Reset1Wire:
   1wreset : 1wwrite &H55 : 1wwrite RegNumber(1) , 8
Return
'Подпрограмма инициализации переменных
' (c)
InitEnv:
    GoSUB Blinking
    SelfAddress = cSelfAddress : SpiralCount = cSpiralCount
    RcvControlSum = 0 : SndControlSum = 0
    ReSet ControlSumError : ReSet ComandCompleted
    ConfirmFromVMC = 0 : SpiralNumber = &HFF : nFromVMC = 1
    'CountRx = 0
    ' Set Lighter
    ' Чтение переключателей
    AllSW = NOT PIND : Shift AllSW , Right , 2
    For I = 1 To 9 : RegAnswer(I) = &HFF : Next
    MotorMod = 0 : MotorCom = 0 : TimeErr = 0 : CountInt = 0 : Ina = 0 : MotorSel = &HFF
    Reset LighterErr                                        ' : Reset Lighter
    GoSUB Blinking
Return
'Подпрограмма обработки срабатывания концевого датчика (c)
ToggleMotor:
   Incr CountInt
   'Print "INT1= " ; CountInt ; "; Mode = " ; Bin(PINB) ; "; Motor5 = " ; Motor5 ; "; Timer = " ; Timer1 ; "; UDR = " ; Ina
   If MotorClick.MotorSel = 1 Then
       Call StopMotor(MotorSel)
   Else
       Set MotorClick.MotorSel
       ' Если таймер еще не сработал, а уже пришло прерывание, то тормозим сразу
       If Motor5 < 1 Then Call StopMotor(MotorSel)
   End If
Return
$include ".\..\TPALibs.bas"
' Старт Мотора (c)
Sub StartMotor(ByVal MotorNum As Byte)                      ' Процедура старта мотора
   Set MotorPort.MotorNum                                   ' Включаем мотор
   TIMER1 = 0 : Set GIFR.7                                  ' Обнуляем таймер
   Waitms cTimerMotor
   Enable INT1                                              ' Разрешаем прерывания
End Sub
' Стоп Мотор (c)
Sub StopMotor(ByVal MotorNum As Byte)                       ' Процедура стоп мотор
   Disable INT1 : Set GIFR.7                                ' Устраняем дребезг прерывания
   MotorClick = 0 : Motor5 = 0 : MotorSel = &HFF
   Reset MotorPort.MotorNum
End Sub
End
$eepromhex
' EEPROM
$eeprom
SelfAddressDefault:
Data &HA0                                                   ' Собственный адрес по умолчанию
SelfSpiralCount:
Data 4
$data