Skip to main content
LibreTexts - Ukrayinska

11.1: Вступ

  • Page ID
    30529
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)

    Іноді приємно переглянути минулий проект, щоб дослідити інший підхід до дизайну. Тут ми збираємося переробляти вправу Таймер реакції. Хоча це, безумовно, було корисним як навчальна вправа, очевидним обмеженням, що стосується самої гри, є той факт, що плата розробки повинна бути підключена до хост-ПК під керуванням програмного забезпечення Arduino, оскільки вона покладалася на послідовний монітор для числового виведення. Замість цього, ми збираємося зробити таймер відповіді, який є автономним. Все, що знадобиться, це джерело живлення. Існує кілька різних способів досягнення числового виходу, мабуть, найбільш прямим є використання семисегментних світлодіодних дисплеїв. Ці пристрої дозволять нам відобразити всі десять цифр плюс кілька алфавітних символів (але не весь англійський алфавіт однозначно).

    Для зручності кодування ми відмовимося від ігрових «доповнень», а саме визначення кращих з п'яти спроб, середнього і так далі; і замість цього зосередитися на сполученні дисплеїв з базовим таймером відгуку. Загальна операція буде подібною: дисплеї будуть швидко блимати, щоб сказати користувачеві «підготуватися», буде випадкова затримка часу від однієї до десяти секунд, тоді дисплеї будуть швидко блимати, як стимул тригера, в цей момент гравець натисне кнопку відповіді (FSR) якомога швидше. Час відгуку буде відображатися на дисплеях протягом декількох секунд і після невеликої паузи процес повториться. Функціональний контур, або псевдокод, дуже схожий на оригінальну гру. Основна проблема полягає в зміні пристрою виведення з Serial Monitor на семисегментні дисплеї, як в апаратному, так і в програмному забезпеченні.

    Що стосується решти апаратного забезпечення, нам більше не знадобляться світлодіоди сигналізації, хоча нам все одно знадобиться перемикач плеєра (FSR). Ми не очікуємо, що час реакції швидше, ніж 100 мілісекунд, і сумнівно, що здорові люди будуть демонструвати час реакції повільніше, ніж одна секунда, припускаючи, що вони не відволікаються або працюють в межах обмежень якоїсь модної хімічної допомоги розваг, яка виконує (сподіваюся) тимчасовий самозавдані пропуски своєї нейронної мережі. Отже, трьох дисплеїв повинно бути достатньо, що дозволяє нам візуалізувати від 0 до 999 мілісекунд.

    Три дисплеї вимагатимуть 21 біт порту, якщо ми керуємо ними самостійно. Це про все, що ми маємо на Arduino Uno, тому це не практичне рішення. Замість цього ми можемо мультиплексувати дисплеї в часі. По суті, ми відобразимо сотні цифр на крайньому лівому дисплеї протягом декількох мілісекунд, потім візуалізуємо десятки цифр на середньому дисплеї протягом декількох мілісекунд і, нарешті, візуалізуємо цифру одиниць на крайньому правому дисплеї протягом декількох мілісекунд. Потім ми повторимо процес. Поки ми зберігаємо загальний час циклу менше, ніж близько 25 мілісекунд, наша обробка очей мозку буде інтегрувати дисплей з часом, так що він здається твердим і непохитним набором з трьох цифр. Для цього нам знадобиться лише десять бітів портів; сім для сегментів і ще три для мультиплексування окремих дисплеїв. Якщо ми використовуємо загальні анодні дисплеї (наприклад, FND507), ми можемо налаштувати їх, як показано на малюнку\(\PageIndex{1}\).

    clipboard_ea83ed9e8178158e8929b29609ed61102.png

    Малюнок\(\PageIndex{1}\): Семисегментна конфігурація дисплея

    Логіка для цієї схеми буде «активним низьким», що означає, що логічний низький запалює сегмент/дисплей, а логічний високий вимикає його. Зверніть увагу, що світлодіодні драйвери тут не показані, скоріше, сегменти підключаються безпосередньо до контактів порту через струмообмежуючі резистори, Rs. Arduino матиме достатню потужність струму для цього, якщо ми збережемо струм сегмента до скромного рівня. Припускаючи, що живлення 5 вольт і близько 2 вольт для світлодіодів, 470\(\Omega\) буде тримати струми приблизно на 6 або 7 міліампер на сегмент. Цього було б достатньо для використання в приміщенні. Окремі дисплеї включаються за допомогою драйверів PNP. Логіка низько на їх базових резисторах розмістить їх у насичення, дозволяючи потік струму на дисплей. Для цього додатка буде достатньо невеликого сигналу PNP, такого як 2N3906. Маючи всі відрізки запалені, проведуть близько 50 міліампер через колектор. Тому значення базового резистора в низькому однозначному\(\Omega\) діапазоні k повинно працювати добре, даючи базовий струм в кілька міліампер.

    Практично кажучи, було б найпростіше присвятити порт D сегментам (D. 1:7, Arduino штифти 1-7). Це означає, що ми втратимо можливість використовувати послідовний монітор (він використовує D. 0:1), але це не повинно бути проблемою. Ми можемо використовувати B. 0:2 (висновки Arduino 8—10) для мультиплексорів цифр і B.3 (контактний 11) для FSR. Деякі #define s були б добре для них. Нам також знадобиться масив бітових шаблонів для дисплеїв:

    // Port B.0:2 for 7 segment mux
    #define DIGIT1   0x01
    #define DIGIT10  0x02
    #define DIGIT100 0x04
    #define DIGIT111 0x07
    
    #define FSRMASK  0x08
    
    unsigned char numeral[]={
       //ABCDEFG,dp
       0b00000011,   // 0
       0b10011111,   // 1 
       0b00100101,   // 2
       0b00001101,   // 3
       0b10011001,
       0b01001001,
       0b01000001,
       0b00011111,
       0b00000001,
       0b00011001,   // 9
       0b11111111,   // blank
       0b01100001,   // E
       0b01110011,   // r
       0b00001001,   // g
       0b00111001    // o  
    };
    
    #define LETTER_BLANK  10
    #define LETTER_E      11
    #define LETTER_R      12
    #define LETTER_G      13
    #define LETTER_O      14
    #define MSG_GO        -1
    #define MSG_ERR       -2
    

    Змінна числівник - це масив, який визначається за допомогою двійкових значень. Кожен біт означає сегмент з MSB є сегментом A (верхній горизонтальний), а LSB - десятковою крапкою (тут не використовується). Наприклад, цифра «0» вимагає, щоб кожен відрізок, окрім G (середня горизонтальна смуга) та десяткової крапки. Оскільки наша схема є активним низьким, ми ставимо 0 на кожен біт, який буде горить. Повторюємо цей процес для решти дев'яти числівників. Ми також додамо кілька спеціальних символів, а саме порожній і літери E, r, g і o Ми будемо використовувати перші два для написання «Err» для помилки (те, що ми звикли називати «обманом»), а другі дві для написання «go», який займе місце колишнього зеленого світлодіода «йти». Ми також визначили пару змінних MSG, які будуть вивчені за мить.

    З огляду на ці визначення, давайте розглянемо, як змінилася функція setup (). Нам потрібно змінити, які шпильки використовуються, але нам все одно потрібне значення насіння для random (). Зверніть увагу, що оскільки ми встановлюємо весь біт порту D у режим виведення, ми можемо просто вибух у 0xff замість побітового ORING конкретних бітів. Також важливо, щоб ми встановили біти мультиплексора (DIGIT111) порту B високо. Оскільки контакти порту D також низькі за замовчуванням, всі дисплеї загоряться, коли плата скидається. Це викличе значний струм через мікроконтролер. Встановлення високих бітів мультиплексора гарантує, що все буде вимкнено (не ввімкнено).

    void setup()
    {
       // set Arduino pins 0 through 7 (port D.0:7) for output displays
       DDRD = 0xff;
    
       // set Arduino pins 8 through 10 (port B.0:2) for output to mux LEDs
       DDRB |= DIGIT111;
    
       // Displays are active low so turn them off initially
       PORTB |= DIGIT111;
    
       // Arduino pin 11, port B.3 for input from FSR
       DDRB &= (~FSRMASK);   // initialize the pin as an input.
       PORTB |= FSRMASK;     // enable pull-up  
    
       // get seed value for random- noise voltage at pin A0
       randomSeed(analogRead( 0 ));
    }
    

    Ось наша основна петля, трохи змінена. Порівняйте його рядок за рядком з оригінальною версією і відзначте будь-які подібності і зміни. Поодинці коментарі повинні надавати достатню деталізацію. Найбільш очевидними змінами є видалення коду Serial Monitor та включення нової функції під назвою displayValue (), яка буде розглянута найближчим часом.

    void loop()
    {
       unsigned long starttime, finishtime;
       int i, j, nettime;
       long a;
      
       for(i=0;i<5;i++)
       {
          a = random(1000, 10000);
    
          // wait a couple of seconds to start this round
          delay(2000);
      
          // flash display a few times to tell player to get ready
          for(j=0;j<5;j++)
          {
             DisplayValue( 888, 100 );
             delay(100);
          }
    
          // delay random amount
          delay(a);
    
          // flash LED for real
          starttime = millis();
          DisplayValue( MSG_GO, 50 );
    
          // wait for response, pressing FSR makes a low
          while( PINB & FSRMASK );
      
          finishtime = millis();
          nettime = finishtime - starttime;
    
          // check for cheat
          if( nettime < 100 )
             nettime = MSG_ERR;
    
          DisplayValue( nettime, 3000 );
       }
    }
    

    Функціонально, DisplayValue () досить простий у використанні. Перший аргумент - це число, яке буде відображатися, від 0 до 999. Друге значення - тривалість відображення в мілісекундах. Отже, DisplayValue (888, 100) означає «відобразити число 888 протягом 100 мілісекунд». Саме ця лінія робить спалах «готуватися». DisplayValue () також дозволяє використовувати кілька спеціальних значень. Вони кодуються як від'ємні значення. Негативний час відгуку неможливий, звичайно, без якоїсь форми здатності подорожувати в часі. Якщо значення встановлено на MSG_ERR, то на дисплеї відображається «Err». Якщо значення встановлено на MSG_GO, то на дисплеї відображається «go». Таким чином, колишнє миготіння зеленого світлодіода «йти» замінюється на DisplayValue (MSG_GO, 50), видаючи повідомлення «йти» протягом 50 мілісекунд.

    Перше, що функція повинна зробити, це перевірити передане їй значення. Він повинен перевірити наявність спеціальних кодів повідомлень, а також значень поза діапазоном. Яке б значення не було передано, ми повинні вивести три нових значення, а саме індекси для числового масиву. Вони зберігатимуться у змінних h, t та u, які стоять на сотні місць, десятків місць та одиниць. Особливі випадки повинні бути перевірені в першу чергу. Крім того, значення потрібно розбити на окремі цифри за допомогою цілих ділень і мод. Послідовні цілочисельні ділення на 10 ефективно зрушать число вниз на місце, тоді як мод на 10 залишить залишок. Подумайте про тризначне число випадковим чином і переконайтеся, що логіка працює.

    void DisplayValue( int v, int msec )
    {
       unsigned char i, h, t, u;
    
       if( (v <= MSG_ERR) || (v > 999) ) // error code
       {
          h = LETTER_E;
          t = u = LETTER_R;
       }
       else
       {
          if( v == MSG_GO )
          {
             h = LETTER_G;
             t = LETTER_O;
             u = LETTER_BLANK;
          }
          else
          {
             u = v%10;
             v = v/10;
             t = v%10;
             h = v/10;
          }
       }
    

    У цей момент нам потрібно перетворити запитувану тривалість в ітерації циклу. Як закодовано, кожна ітерація триває близько 15 мілісекунд, враховуючи, що кожна цифра висвітлюється протягом 5 мілісекунд. Простого поділу буде достатньо, хоча ми можемо захотіти захиститися від того, хто випадково запитує лише кілька мілісекунд (що дасть нуль).

       // turn millisecs into loop iterations
       msec = msec/15;
       if( msec < 1 )
          msec = 1;
    

    Тепер ми створюємо цикл, який приблизно дасть запитувану тривалість з роздільною здатністю 15 мілісекунд. Усередині циклу спочатку очищаємо всі дисплеї. Потім включаємо потрібний дисплей і вибухаємо в код для числівника. Зверніть увагу, що, вводячи бінарні значення в масив у порядку зростання, ми можемо просто використовувати обчислене значення цифри як індекс в масив. Нам не потрібно переводити потрібний числівник у відповідний індекс. Трохи передбачливості може заощадити зусилля кодування і простір пам'яті. Кожна цифра тримається протягом 5 мілісекунд. При закритті функції відключаємо дисплей.

       // display the value for the specified time
       for( i=0; i<msec; i++ )
       {
          // clear all displays then activate the desired digit
          PORTB |= DIGIT111;
          PORTD = numeral[h];
          PORTB &= ~DIGIT100;
          delay(5);
    
          PORTB |= DIGIT111;
          PORTD = numeral[t];
          PORTB &= ~DIGIT10;
          delay(5);
    
          PORTB |= DIGIT111;
          PORTD = numeral[u];
          PORTB &= ~DIGIT1;
          delay(5);   
       }
      
       // clear display
       PORTB |= DIGIT111;