Визуальное представление
Описание
Устройство представляет собой программируемый кнопками S1-S3 Двух фазный генератор от 1Гц до 9999Гц, информация выводится на символьный LCD1602. В программном коде используются оба канала 16-битного таймера (см. рисунок 3 - Timer1). Сдвиг фазы устанавливается у канала В. Arduino UNO можно заменить на NANO или любую другую особых изменений не требует, но будьте внимательны!
*будете внимательны https://www.tinkercad.com/ нет библиотеки GyverTimers.h.
В схеме предусмотрена регулировка, с помощью "ШИМ", яркости и контрастности (функция DISPLAY_AND_CONTRAST() - используется по необходимости). Так же программно возможно установить сдвиг фазы у обоих каналов, но частота для обоих каналов устанавливается одна (так как используется 1 таймер), Во время настройки, оба канала выключены, после установки необходимых значений каналы включаются, что свидетельствует изменение в правом верхнем углу "ON" и "OFF".
Для регулировки частоты каждого канала - придется привнести некоторые не значительные изменения:
Необходимо для 2 выхода использовать любой другой таймер (рекомендую канал B если не вчитываться в тонкости) ориентируясь по таблице (ссылка от автора GyverTimers.h) - https://alexgyver.ru/gyvertimers/
Рисунок 3 - таблица таймеров ATmega328p *Обратите внимание на минимальную частоту таймеров.
Изменить «контент» на LCD, проще использовать LCD1604, или придумать удобное визуальное представление, для ручной настройки частоты и сдвига обоих таймеров.
*Обратите внимание, что сдвиг фазы будет относительно самого таймера (и используемого канала), а не между ними (между таймерами). Хоть программно запуская оба таймера условно можно принять на веру их синхронность, однако это будет не совсем корректно.
Изменить карту перемещения курсора, его координаты. Программный код обязан справится с изменениями первоначальных данных, Вы можете описать то представление информации на LCD, какое удобно Вам, а так же перемещение курсора или установка большей частоты.
Для увеличения регулируемого диапазона частоты необходимо увеличить "cегментность" (количество разрядов) или произвести переключение с Гц на КГц, но помните что макс частота 1МГц(см. Рисунок 3)., придется так же изменить незначительно программный код, с теми условиями которые необходимы Вам, к примеру:
По нажатию на Hz изменить на KHz,
В коде изменить изначально на KHz
По вашему усмотрению.
ИТОГ
Простое устройство паразитирующее библиотеку "GyverTimers.h", которая сильно упрощает работу с таймером. Программный код разделен на несколько условных блоков "работа с LCD" и "настройка таймера".
Программный код (Arduino C ):
*Обратите внимание что с вкл LCD, питая всю плату о USB порта компьютера, вполне возможно произойдет ситуация, что Arduino не будет прошиваться, отключите LCD, во время загрузки прошивки.
/****************************************************/
/* 2-х фазный генератор */
/* */
/* */
/* */
/****************************************************/
// подключение библиотек
#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(); // этот цикл завершен
}
}
/****************************************************/
Признаю, что все можно сделать лучше, но я себе поставил задачу сделать как можно "more universal" - по минимум изменений. Надеюсь на 3+ выполнил.=)