2-phase generator 1Hz-9999Hz on Arduino with the ability to adjust the phase shift using the GyverTimers.h library

visual presentation

Figure 1 - Conditional connection diagram

Figure 1 – Conditional connection diagram

Figure 2 - circuit diagram

Figure 2 – circuit diagram

Description

The device is a two-phase generator programmable with S1-S3 buttons from 1Hz to 9999Hz, information is displayed on a character LCD1602. The code uses both channels of the 16-bit timer (see Figure 3 – Timer1). The phase shift is set at channel B. Arduino UNO can be replaced with NANO or any other does not require any special changes, but be careful!

Project link.

*be careful https://www.tinkercad.com/ there is no GyverTimers.h library.

The circuit provides for the adjustment, using “PWM”, brightness and contrast (function DISPLAY_AND_CONTRAST () – used if necessary). It is also possible to programmatically set the phase shift for both channels, but the frequency for both channels is set to the same (since 1 timer is used), During setup, both channels are turned off, after setting the required values, the channels turn on, which indicates a change in the upper right corner “ON ” and “OFF”.

To adjust the frequency of each channel – you will have to make some minor changes:

  1. It is necessary to use any other timer for output 2 (I recommend channel B if you do not read the details) based on the table (link from the author GyverTimers.h) – https://alexgyver.ru/gyvertimers/

    Figure 3 - ATmega328p timer table

    Figure 3 – ATmega328p timer table

    *Pay attention to the minimum frequency of the timers.

  2. Change the “content” on the LCD, it’s easier to use the LCD1604, or come up with a convenient visual representation to manually adjust the frequency and shift both timers.

    *Please note that the phase shift will be relative to the timer itself (and the channel being used), not between them (between timers). Although by programmatically launching both timers it is conditionally possible to take their synchronism for granted, but this will not be entirely correct.

  3. Change the cursor movement map, its coordinates. The program code must cope with changes in the original data, you can describe the presentation of information on the LCD, which is convenient for you, as well as moving the cursor or setting a higher frequency.

To increase the frequency, you need to increase the “segmentation” (number of digits) or switch from Hz to kHz, but remember that the maximum frequency is 1 MHz, you will also have to slightly change the program code, with the conditions that you need, for example:

  1. By pressing Hz change to KHz,

  2. In the code, change initially to KHz

  3. At your discretion.

TOTAL

A simple device that parasitizes the “GyverTimers.h” library, which greatly simplifies the work with the timer. The program code is divided into several conditional blocks “working with LCD” and “setting the timer”.

Program code (Arduino C ):

*Please note that with the LCD on, powering the whole board from the USB port of the computer, it is quite possible that the Arduino will not flash, turn off the LCD while the firmware is downloading.

/****************************************************/
/*               2-х фазный генератор               */
/*                                                  */
/* Библиотека GyverTimers                           */
/* описание работы библиотекиGyverTimers            */
/****************************************************/
// подключение библиотек
#include <GyverTimers.h>    // Библиотека GyverTimers (для работы с таймером И! устанавливать сдвиг фазы)
#include <LiquidCrystal.h>  // Подключаем стандартную библиотеку LiquidCrystal

/****************************************************/
// Инициализируем объект-экран, передаём использованные
LiquidCrystal lcd(8, 7, 15, 14, 11, 12);  // RS, E, D4, D5, D6, D7 */

/****************************************************/
// Инициализация пинов
#define LEFT_BUTTON 4                // левая кнопка  
#define RIGHT_BUTTON 2               // права кнопка           
#define CENTRE_BUTTON 3              // центральная кнопка 
#define DISPLAY_CONTRAST_ON 5        // Управление контрасностью ШИМ
#define DISPLAY_LIGHT_ON 6           // Управление подсветкой ШИМ 
#define CHANNEL_TIMER_A 9            // выход канала А 1 таймера
#define CHANNEL_TIMER_B 10           // выход канала В 1 таймера

/****************************************************/
//таймер
#define AMOUNT_VALUE_FREQUENCY_NUMBER 4      // количество знаков значения частоты обоих каналов
#define AMOUNT_VALUE_FREQUENCY_DEVIATION 2   // количество знаков значения отклонения частоты канала В
#define ON_TEXT "ON "                        // Текст на LCD для отображения того что каналлы вкл
#define OFF_TEXT "OFF"                       // Текст на LCD для отображения того что каналлы выкл
  
/****************************************************/
// Положение курсора 
int cur_col = 0;                     // текущая координата указателя (столбец)
int cur_row = 0;                     // текущая координата указателя (строка)  

/****************************************************/
// LCD
// для использования другого необходимо отредактировать:
#define NUMBER_COL_USE_LCD 16        // количетсво столбцов (установить от 1) но в дальнейшем использовать от 0
#define NUMBER_ROW_USE_LCD 2         // количетсво строк (установить от 1) но в дальнейшем использовать от 0
int number_row[]  =                  // номера строк LCD ( используется для курсора)
{ 0, 1};

/****************************************************/
// контент LCD
// **страница 1**
// внешний вид страницы 1
String content_page_one[] =          // массив контента 1 страницы
{ "Ch1:0100Hz ->ON ",                // 0 строка
  "F1:180'"};                        // 1 строка

int position_cursor_page_one[4][4] = // координтаы по которому необходимо перемещать курсор( 1 индекс = строка) (2 индекс = столбец)                  
{
  { 4, 5, 6, 7},                     // Координаты 1 ряда 1 страницы (0-3)
  { 3, 4, 5},                        // Координаты 2 ряда 1 страницы (4-5) 
};  
int amount_position_page_one[] =     // Количество(размер массива) координат на одной линии
  { 3, 2};                           //( так как самого начала он должен быть известен)( sizeoof - работает во время компиляции, а не кода)
     
// Значения(VALUE) используемые на странице 1 (индекс = разряду( начало с конца)
int value_content_one_page[][4] = 
{
  { 0, 1, 0, 0},                     // частота канала A и B
  { 1, 8, 0},                        // сдвиг по фазе канала  B
};
int amount_value_page_one[] =        // Количество(размер массива) изменяемых значений на одной линии
  { 3, 2};  

/****************************************************/
// Символы ASCII начиная с 0 
#define translate_to_ASC 48          // 0  в кодировке ASCII

/****************************************************/
// Работа со временм
#define last_press_For_buttom  200   // условный антидребезг контактов
#define micros_work 300              // время 1 цикла (loop)( перемещение курсора или редактирование)
unsigned long Work;                  // переменная для сравнения истекшего отрезка времени в loop
unsigned long last_press;            // Время Прошлого нажатия
/****************************************************/
// Флаги 
boolean flag_editor_enable ;         // включен редактор одного символа на lcd
/****************************************************/

/****************************************************/
/****************  РАБОТА С LCD  ********************/
/****************************************************/
//--------------------------------------------------//
// функция для перевода int в число из кодировки ASCII 
char func_change_int_to_ASC(int param0){return param0 + translate_to_ASC;}
//--------------------------------------------------//
// функция для перевода из кодировки ASCII в int
int change_ASC_to_int(char param){return param - translate_to_ASC;}
//--------------------------------------------------// 
// функция установки контента на LCD                                          
void load_page_to_lcd(String *array_content){       // массив в котором хранится контент 
  lcd.clear();                                      // очистить LCD
  for (int i=0; i < NUMBER_ROW_USE_LCD ;i++){       // выводим текст равный ( инициализированному значению страницы для LCD)
    lcd.setCursor(0,i);
    lcd.print(array_content[i]);
  }
} 
//--------------------------------------------------//
// функция опроса кнопки 
boolean ask_button(int number_pin){
  boolean is_button_press = false;
  if (!digitalRead(number_pin) && (millis() - last_press > last_press_For_buttom)){  // проверка на нажатие  защита от антидребезг + множетсвенное нажатие
    is_button_press = true;
    last_press = millis();
  }
  return is_button_press;
}
//--------------------------------------------------//
// функция установки курсора
void set_cursor(int param0,int param1){             // param0 = col, param1 = row
  lcd.setCursor(param0,param1);
  lcd.cursor();                 // подчеркивание
}
//--------------------------------------------------//
// функция перемещения курсора
void move_cursor(){
  if(ask_button(LEFT_BUTTON)){       // нажата левая кнопка 
    int cur_index_col = find_index(position_cursor_page_one[cur_row],
      amount_position_page_one[cur_row],cur_col);                       // найти  текущий индекс числа (столбца) 
    cur_index_col --;                                                   // уменьшить текущий индекс
    if(is_index_exp(amount_position_page_one[cur_row],cur_index_col)){  // если надо перенести строку
      cur_row --;                                                       // опустить курсор выше
      if(is_index_exp(NUMBER_ROW_USE_LCD - 1, cur_row)){                // надо ли перейти в начало или конец страницы
        cur_row = number_row[NUMBER_ROW_USE_LCD - 1];                   // установить последнюю строку
      }
      cur_col = position_cursor_page_one[cur_row][              
        amount_position_page_one[cur_row]];                             // получить индекс последнего элемента ({[текущая строка] [размер массива])                       
    }
    else{                                                               // если НЕ надо переносить строку и перехоить в начало или конец
      cur_col= position_cursor_page_one[cur_row][cur_index_col];        // присвоить новое текущее значение
    }
    set_cursor(cur_col,cur_row);     // установить новые координаты для курсора
  }
  
  else if(ask_button(RIGHT_BUTTON)){ // нажата правая кнопка 
    int cur_index_col = find_index(position_cursor_page_one[cur_row],
      amount_position_page_one[cur_row],cur_col);                       // найти  текущий индекс числа (столбца) 
    cur_index_col ++;                                                   // увеличить текущий индекс
    if(is_index_exp(amount_position_page_one[cur_row],cur_index_col)){  // если надо перенести строку
      cur_row ++;                                                       // опустить курсор ниже
      if(is_index_exp(NUMBER_ROW_USE_LCD - 1, cur_row)){                // надо ли перейти в начало или конец страницы
        cur_row = number_row[0];                                        // установить на первую строку
      }
      cur_col = position_cursor_page_one[cur_row][0];                   // индекс первого элемента ({[текущая строка] [размер массива])                         
    }
    else{                                                               // если НЕ надо переносить строку и перехоить в начало или конец
      cur_col= position_cursor_page_one[cur_row][cur_index_col];        // присвоить новое текущее значение
    }
    set_cursor(cur_col,cur_row);     // установить новые координаты для курсора
  }
}
//--------------------------------------------------//
// функция нахождения индекса значения в массиве int( если не найдет вернет 9999 - условная ошибка)
int find_index(int *received_array,int syze_array,int number){
  for(int i=0 ;i <= syze_array;i++)if(received_array[i] == number)return i;
  return 9999;
}
//--------------------------------------------------//
// функция проверки если индекс вышел из диапазона( IndexOutOfRangeException )
boolean is_index_exp(int syze_array, int index){
  if(syze_array < index or index < 0) return true;
  else {return false;}
}
//--------------------------------------------------//
// функция изменения 1 значения(разряда) на LCD
void change_one_segment_on_lcd(){
  if(ask_button(LEFT_BUTTON)){                            // нажата левая кнопка    
    int buff = value_content_one_page[cur_row][
      find_index(position_cursor_page_one[cur_row],
        amount_value_page_one[cur_row],cur_col)];         // взять текущее значение 
    buff++;                                               // увеличить на 1
    buff = func_zero_nine(buff);                          // проверить
    lcd.print(func_change_int_to_ASC(buff));              // отобразить
    set_cursor(cur_row,cur_col);                          // вернуть курсор
    value_content_one_page[cur_row][
      find_index(position_cursor_page_one[cur_row],
        amount_value_page_one[cur_row],cur_col)] = buff;  // сохранить
  }
  else if(ask_button(RIGHT_BUTTON)){                      // нажата правая кнопка
    int buff = value_content_one_page[cur_row][
      find_index(position_cursor_page_one[cur_row],
        amount_value_page_one[cur_row],cur_col)];         // взять текущее значение 
    buff--;                                               // уменьшить на 1
    buff = func_zero_nine(buff);                          // проверить
    lcd.print(func_change_int_to_ASC(buff));              // отобразить
    set_cursor(cur_row,cur_col);                          // вернуть курсор
    value_content_one_page[cur_row][
      find_index(position_cursor_page_one[cur_row],
        amount_value_page_one[cur_row],cur_col)] = buff;  // сохранить
  }
}
//--------------------------------------------------//
// фукция изменения числа(цикла) 0-9 
int func_zero_nine(int number){
  switch(number){
    case 10: return 0; break;
    case -1: return 9; break;
    default: return number; break;
  }
}
//--------------------------------------------------//
void on_off_lcd_for_page_one(String param0){
  lcd.setCursor(13,0);
  lcd.print(param0);
}
//--------------------------------------------------//
// Управление подсветкой и контрастностью ( не используется )
void DISPLAY_AND_CONTRAST()  { 
  //analogWrite(DISPLAY_LIGHT_ON,0);     // Вкл ШИМ подсветки
  //analogWrite(DISPLAY_CONTRAST_ON,0);  // Вкл ШИМ контрасности          
}
/****************************************************/
/*********************  ГЕНЕРАТОР  ******************/
/****************************************************/
//--------------------------------------------------//
// запуск таймера
void start_timer(){
  Timer1.enableISR(CHANNEL_A);                      // запускаем таймер 1 канал А
  Timer1.enableISR(CHANNEL_B);                      // запускаем таймер 1 канал B
  Timer1.setFrequency(get_freq());                  // установить частоту
  Timer1.phaseShift(CHANNEL_B,get_dem_freq());      // установить сдвиг фазы
}
//--------------------------------------------------//
// настройка таймера
void set_timer(){
  Timer1.outputEnable(CHANNEL_A, TOGGLE_PIN);       // в момент срабатывания таймера пин будет переключаться
  Timer1.outputEnable(CHANNEL_B, TOGGLE_PIN);       // в момент срабатывания таймера пин будет переключаться
  Timer1.outputState(CHANNEL_A, HIGH);              // задаём начальное состояние пина 11
  Timer1.outputState(CHANNEL_B, HIGH);              // задаём начальное состояние пина 3
}
//--------------------------------------------------//
// прерывание таймера TIMER1_A
ISR(TIMER1_A) {
  // код по обработке прерывания
  // необходимо объявить даже если тело пустое для GyverTimers.h
}
//--------------------------------------------------//
// прерывание таймера TIMER1_B
ISR(TIMER1_B) {
  // код по обработке прерывания
  // необходимо объявить даже если тело пустое для GyverTimers.h
}
//--------------------------------------------------//
// получение значения частоты
int get_freq(){
  return (value_content_one_page[0][0] * 1000 +
    value_content_one_page[0][1] * 100 +
      value_content_one_page[0][2] * 10 +
        value_content_one_page[0][3]) * 2;
        // неободимо умножать на 2 частоту( если меанд)
        // причина в работе таймера и GyverTimers.h
}
// получение значения сдвига фазы
//--------------------------------------------------//
int get_dem_freq(){
  return value_content_one_page[1][0] * 100 +
    value_content_one_page[1][1] * 10 +
      value_content_one_page[1][2];
}

/****************************************************/
/********************  MAIN  ************************/
/****************************************************/
//--------------------------------------------------//
// SETUP
void setup() {
  lcd.begin(NUMBER_COL_USE_LCD, NUMBER_ROW_USE_LCD);  // Инициализируем дисплей: 
  pinMode(LEFT_BUTTON, INPUT);                        // Инициализируем кнопки:
  pinMode(RIGHT_BUTTON, INPUT);      
  pinMode(CENTRE_BUTTON, INPUT);       
  
  pinMode(DISPLAY_CONTRAST_ON, OUTPUT);               // управление подсветкой 
  pinMode(DISPLAY_LIGHT_ON, OUTPUT);                  // управление контрасностью 

  pinMode(CHANNEL_TIMER_B, OUTPUT);                   // Выход таймера канал B
  pinMode(CHANNEL_TIMER_A, OUTPUT);                   // Выход таймера канал A 

  digitalWrite(14, LOW);                              // ( перевод в цифровое состояние и записать 0) пина 14(А0)
  digitalWrite(15, LOW);                              // ( перевод в цифровое состояние и записать 0) пина 15(А1)
 
  set_timer();                                        // настройка таймера 
  start_timer();                                      // запуск таймера ( с начальной частотой и отклонением )
 
  load_page_to_lcd(content_page_one);                 // загрузить 1(начальную) страницу
  cur_row = number_row[0];                            // задать начальные координаты курсора (строка)
  cur_col = position_cursor_page_one[0][0];           // задать начальные координаты курсора (столбец)
  set_cursor(cur_col,cur_row);                        // установить курсор 
  flag_editor_enable = false;                         // выключить редактирование (включить перемещение курсора)
}
/****************************************************/
//--------------------------------------------------//
// LOOP
void loop() {
  if (micros() - Work > micros_work){     // Выполнять цикл раз в (micros_work)  (Работа с LCD дисплеем)
    if(flag_editor_enable){               // изменение 1 сегмента   
      lcd.blink();                        // выключить мигание
      set_cursor(cur_col,cur_row);        // установить курсор 
      change_one_segment_on_lcd();        // Опрос боковых кнопок ( для изменения)
      if(ask_button(CENTRE_BUTTON)){      // опрос центральной кнопки
        flag_editor_enable = false;       // выключить редактирование
        on_off_lcd_for_page_one(ON_TEXT); // отобразить на дисплее включение
        Timer1.setFrequency(get_freq());  // установить частоту и запустить таймер
        Timer1.phaseShift(
          CHANNEL_B,get_dem_freq());      // установить сдвиг фазы
      }
    }
    else{                                 //перемещение курсора
      lcd.noBlink();                      // включить мигание
      set_cursor(cur_col,cur_row);        // установить курсор 
      move_cursor();                      // Опрос боковых кнопок ( для перемещения)
      if(ask_button(CENTRE_BUTTON)){      // опрос центральной кнопки
        flag_editor_enable = true;        // включить редактирование
        on_off_lcd_for_page_one(OFF_TEXT);// отобразить на дисплее выключение
        //Timer1.pause();                 // приостановить
        Timer1.stop();                    // приостановить и сбросить
      }
    }
    Work = micros();                      // этот цикл завершен
    }  
}

/****************************************************/

I admit that everything can be done better, but I set myself the task of making as much as possible “more universal” – with a minimum of changes. I hope for 3+ completed. =)

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *