ИМС TEA5767 производимая компанией NXP применяется для конструирования низковольтных FM-радио тюнеров. В составеTEA5767 имеются внутренние цепи выделения промежуточной частоты и демодуляции принимаемого сигнала, что позволяет обходиться минимальным набором внешних компонентов.
Технические параметры TEA5767:
- Напряжение питания от 2,5 до 5 В
- Потребляемый ток при Uпит = 5 В 12,8 мА
- Чувствительность 2 мкВ
- Отношение сигнал/шум 54 дБ
- Разделение между стереоканалами 24 дБ
- Коэффициент гармоник 0,4 %
- Диапазон принимаемых частот от 76 МГц до 108 МГц
- Шины управления: I2C или 3-х проводная
- Функция автоматической настройки на принимаемые радиостанции
- Автоматическое стереодекодирование принятого сигнала
Радио модуль TEA5767 управляется всего двумя кнопками «+» и «-«, которые используются для поиска необходимой станции. Через 10 секунд после не активности кнопок, частота выбранного канала сохраняется в энергонезависимой памяти.
Библиотека — TEA5767.zip
#include <TEA5767.h> #include <Wire.h> #include <LiquidCrystal.h> #include <EEPROM.h> TEA5767 Radio; LiquidCrystal lcd(7, 6, 2, 3, 4, 5); unsigned long last_pressed,time; unsigned char buf[5]; int stereo,signal_level,search_mode = 0,search_direction,i,f_h,f_l,f,w; double current_freq; float f_new = (EEPROM.read(0)*256 + EEPROM.read(1)); byte a1[8]={0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111}; byte a2[8]={0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111}; byte a3[8]={0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111}; byte a4[8]={0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111}; byte a5[8]={0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111}; byte a6[8]={0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}; byte a7[8]={0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}; byte a8[8]={0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}; void setup() { Serial.begin(9600); Wire.begin(); Radio.init(); Radio.set_frequency(f_new/10); Serial.begin(9600); lcd.begin(16,2); lcd.clear(); pinMode(12,INPUT); pinMode(11,INPUT); lcd.createChar(0,a1);lcd.createChar(1,a2);lcd.createChar(2,a3);lcd.createChar(3,a4);lcd.createChar(4,a5);lcd.createChar(5,a6);lcd.createChar(6,a7);lcd.createChar(7,a8); } void loop() { if (Radio.read_status(buf) == 1) { current_freq = floor (Radio.frequency_available (buf) / 100000 + .5) / 10; stereo = Radio.stereo(buf); signal_level = Radio.signal_level(buf); lcd.setCursor(0,0); lcd.print("FM "); lcd.print(current_freq);lcd.print(" MHz "); lcd.setCursor(0,1); if (stereo) lcd.print("STEREO "); else lcd.print("MONO "); for(i=0;i<8;i++){if(signal_level>=8+i){lcd.setCursor(8+i,1);lcd.write((uint8_t)i);}} } if (search_mode==1){if(Radio.process_search(buf, search_direction)==1){search_mode = 0;}} if (digitalRead(11)==HIGH) { search_mode = 1;time=millis();w=1; search_direction = TEA5767_SEARCH_DIR_UP; Radio.search_up(buf); delay(200); } if (digitalRead(12)==HIGH) { search_mode = 1;time=millis();w=1; search_direction = TEA5767_SEARCH_DIR_DOWN; Radio.search_down(buf); delay(200); } f = current_freq*10; f_h = highByte(f);f_l = lowByte(f); if(millis()-time>10000&&w==1){w=0; EEPROM.update(0,f_h);EEPROM.update(1,f_l);} delay(50);if(search_mode==1){lcd.clear();} }
Я не только визуально просмотрел но и прозванил мультиметром Буду думать и искать.
Я мог ошибится в одной конструкции а вторая таже самая история
к вечеру отпишусь
Может дело не в дисплее и подключении, может у Вас один из пинов ардуино не рабочий, такое тоже может быть. Впрочем проверить это не сложно.
Последний вопрос в коментариях у китайца(продавца) есть пометочка — адрес экрана — 0х27
незнаю что это и как проверить.Я вчера все проверил и повторно все пропаял результат 0
кстати на втором приемнике тоже самое — я брал у одного продавца 4 ардуинки две работают в другом проэкте а две в приемниках не хотят что то я сомневаюсь.
Это I2C адрес дисплея с модулем I2C. У Вас дисплей не имеет адреса, так на нем не установлен I2C модуль.
Есть новая Ардуино Нано как проверить пины подскажите пожалуйста?
С новой ардуино тоже самое уже незнаю на что думать
может что с дисплеем?
Я совсем запутался посмотрите пожалуйста.
lcd | arduino
vss = gnd
vdd = 5v
vo = контраст
rs = d7
rw = gnd
e = d6
d4 = d2
d5 = d3
d6 = d4
d7 = d5
Спасибо за время потраченное на меня, ларчик просто открывался получил два новых дисплея от другого продавца и все заработало.
На всякий случай ссылка на продавца приславшего 2 негодных дисплея: https://aliexpress.ru/item/32413056677.html?spm=a2g2w.orderdetail.0.0.1a8b4aa6dZpupF&sku_id=65860136348&_ga=2.244208775.345815471.1699808386-1262275141.1699207505
Здравствуйте.LGT8F328P-LQFP32вот на этой плате будет работать?
Должно.
Что то на дисплее ничего не отображается. Скомпилировалось без ошибок. Загрузка успешна. И всё. Пустой экран
Разобрался. Питание не контачило. Только теперь частота сама бежит и не останавливается
Кнопки точно правильно запаяны? На размыкаемые пары?
Мы имеем скетч для управления модулем TEA5767 с Arduino и дисплеем LCD 16×2.
В коде есть несколько моментов, которые стоит улучшить или исправить:
1. Инициализация частоты из EEPROM:
— Переменная `f_new` инициализируется как глобальная, но при этом используется значение из EEPROM.
— Однако, в EEPROM записываются два байта (старший и младший) для хранения частоты (умноженной на 10, чтобы хранить как целое).
— При запуске мы читаем EEPROM и формируем `f_new = (EEPROM.read(0)*256 + EEPROM.read(1))`, затем делим на 10 при установке частоты: `Radio.set_frequency(f_new/10);`
— Проблема: если EEPROM не инициализирована, то будет установлена неправильная частота.
2. Переменная `time` не инициализирована. В коде есть проверка `if(millis()-time>10000 && w==1)`, но `time` изначально не установлена. Нужно инициализировать `time = millis();` в setup.
3. Переменная `w` не инициализирована. В коде она используется как флаг, чтобы записать частоту в EEPROM только один раз после 10 секунд. Но изначально `w` не установлена. Лучше инициализировать `w=0;` в глобальной области или в setup.
4. При нажатии кнопок (пины 11 и 12) устанавливается `w=1` и `time=millis()`, и через 10 секунд (если не было других нажатий) частота записывается в EEPROM.
— Однако, если частота меняется не кнопками, а другим способом, то запись не произойдет.
5. При поиске (search_mode=1) происходит очистка экрана `lcd.clear();` в цикле, что может привести к мерцанию.
6. В коде дважды вызывается `Serial.begin(9600);` в setup.
7. В коде есть глобальная переменная `last_pressed`, которая не используется.
8. Нет подтягивающих резисторов для кнопок. В коде используется `digitalRead` для пинов 11 и 12, но не включены внутренние подтягивающие резисторы.
Можно включить в setup: `pinMode(11, INPUT_PULLUP);` и аналогично для 12.
9. Проверка нажатия кнопок: если кнопки подключены на замыкание на землю, то условие `digitalRead(11)==HIGH` будет срабатывать, когда кнопка не нажата (если используется INPUT_PULLUP).
Поэтому, если кнопка подключена между пином и землей, то при нажатии будет LOW. Нужно изменить условие.
10. В блоке обработки поиска: после установки search_mode=1, мы вызываем Radio.search_up(buf) или Radio.search_down(buf).
Затем в основном цикле, если search_mode==1, то вызывается `Radio.process_search(buf, search_direction)`, которая возвращает 1, когда поиск завершен.
11. При завершении поиска (process_search вернул 1) мы сбрасываем search_mode, но не записываем частоту в EEPROM?
Запись в EEPROM происходит только по таймауту 10 секунд бездействия. Это нормально.
12. В коде есть переменные `f_h` и `f_l`, которые используются для записи в EEPROM. Они вычисляются из `current_freq*10`.
Но current_freq — это double, умножение на 10 и приведение к int может быть неточным. Лучше использовать округление.
13. Задержка `delay(50);` в цикле может быть уменьшена, чтобы сделать систему более отзывчивой.
14. При поиске экран очищается каждый раз в цикле, что может привести к мерцанию. Лучше очищать экран только при начале поиска.
15. В коде используется библиотека TEA5767.h. Убедитесь, что используется версия, поддерживающая функции `read_status`, `frequency_available`, `stereo`, `signal_level`, `process_search`, `search_up`, `search_down`.
16. В блоке вывода уровня сигнала на LCD:
«`
for(i=0;i=8+i){lcd.setCursor(8+i,1);lcd.write((uint8_t)i);}}
«`
Это создает шкалу из 8 столбиков. Каждый столбик отображается символом, который создан ранее (a1..a8).
Однако, если уровень сигнала меньше 8, то столбики не отобразятся. А если больше 15, то выйдет за пределы строки.
Уровень сигнала: `signal_level` возвращает значение от 0 до 15 (библиотека TEA5767).
Мы выводим с 8-й позиции второй строки. Для каждого из 8 столбиков (i от 0 до 7) условие: если signal_level >= (8+i), то выводим символ i.
Например, для signal_level=10:
i=0: 10>=8 -> выводим символ0 на позицию (8,1)
i=1: 10>=9 -> выводим символ1 на позицию (9,1)
i=2: 10>=10 -> символ2 на позицию (10,1)
i=3: 10>=11 -> нет
…
Таким образом, будет отображено 3 столбика.
Но это не совсем интуитивно. Обычно шкалу делают пропорциональной. Здесь же каждый столбик требует на 1 единицу больше.
17. Для сохранения EEPROM: запись происходит каждые 10 секунд бездействия. Но если частота меняется часто, то это может привести к износу EEPROM.
Лучше записывать только когда частота изменилась и прошло время.
18. В коде глобальная переменная `i` используется в нескольких циклах. Это не ошибка, но лучше объявлять `i` локально в циклах.
Рекомендации по исправлению:
1. Инициализация EEPROM: при первом запуске, если в EEPROM нет значения (например, 0xFFFF), то установить частоту по умолчанию.
2. Исправить инициализацию переменных:
— В setup: `time = millis();`
— В глобальных: `int w = 0;`
3. Для кнопок: использовать внутренние подтягивающие резисторы и инвертировать условие.
4. Убрать лишний `Serial.begin(9600);`.
5. Оптимизировать отображение при поиске: очищать экран только при начале поиска, и выводить сообщение о поиске.
6. Увеличить отзывчивость: уменьшить задержку или использовать неблокирующие задержки.
7. Переписать отображение шкалы сигнала: сделать так, чтобы отображалось количество столбиков, пропорциональное уровню сигнала (от 0 до 8).
8. Для EEPROM: записывать только при изменении частоты и по истечении 10 секунд без изменений.
9. Убрать неиспользуемые переменные.
10. Исправить вычисление частоты для записи в EEPROM: округлить до целого.
Внесем изменения в код:
— Добавим частоту по умолчанию (например, 87.5 МГц) на случай, если EEPROM не инициализирована.
— Исправим чтение EEPROM: будем хранить частоту в виде целого числа (умноженной на 10) и проверять, не равно ли оно 0xFFFF (или 65535) — но EEPROM изначально 255, так что можно считать, что если оба байта 255, то это первый запуск.
— Включим подтягивающие резисторы для кнопок и изменим условие на LOW.
— Инициализируем `time` и `w`.
— Уберем лишний `Serial.begin`.
— Изменим отображение шкалы сигнала: будем отображать `signal_level` в виде 8 сегментов, разделив диапазон 0-15 на 8 частей. Каждый сегмент будет соответствовать 2 единицам сигнала (потому что 16/8=2).
Тогда количество закрашенных столбиков = min(8, signal_level / 2) (но деление целочисленное). Однако текущий метод тоже работает, но непрозрачно. Оставим как есть, или переделаем.
Решено: оставим текущий метод, так как он уже рисует ступенчато. Но для ясности, можно переделать на:
int bars = map(signal_level, 0, 15, 0, 8);
for (int i=0; i<8; i++) {
if (i = (8+i).
Т.е. для signal_level=0..7 ничего не выводится, 8 -> символ0 (1 пиксель), 9 -> символ0 и символ1 (1 и 2 пикселя) и т.д.
Это нелинейно и не очень наглядно.
Альтернатива: создать 8 символов, которые представляют собой шкалу из 8 делений (каждый символ — один столбик, который может быть заполнен на 1/8, 2/8 и т.д.).
Но это сложно, так как у нас только 8 символов, а нам нужно 8 разных состояний для каждого столбика? Нет, мы можем использовать один символ для каждого уровня в столбике.
Другой вариант: использовать стандартные символы, но тогда шкала будет состоять из 8 одинаковых блоков, каждый из которых может быть заполнен на 1, 2, … 8 пикселей?
Но у нас только 8 пользовательских символов, и мы их все использовали для одного столбика разной высоты.
Текущий метод: каждый столбик — это один символ, и высота столбика увеличивается с каждым символом. Но на самом деле, в каждой позиции мы выводим символ, который имеет высоту от 1 до 8 пикселей.
И мы выводим в каждой из 8 позиций по столбику, причем столбик в позиции i (слева направо) будет отображаться только если уровень сигнала >= (8+i).
То есть первый столбик (i=0) отображается при уровне 8, а при уровне 15 отображаются все 8 столбиков.
Это означает, что шкала начинает отображаться с уровня 8. А уровни 0-7 не отображаются.
Исправим: будем отображать шкалу для всего диапазона 0-15. Разобьем на 8 уровней:
уровень 0: ничего
уровень 1: 1 столбик (первый) с высотой 1 пиксель
…
уровень 15: 8 столбиков, каждый высотой 8 пикселей?
Но у нас на каждый столбик отведен один символ. И мы не можем менять высоту каждого столбика независимо.
Поэтому переделаем: мы создали 8 символов, каждый из которых — это столбик разной высоты (от 1 до 8 пикселей). И мы хотим отобразить 8 таких столбиков (по горизонтали).
Для каждого столбика мы решаем, какой высоты его отображать? Но у нас только 8 символов, и каждый символ — это один вариант высоты.
Идея: пусть каждый столбик соответствует своему уровню. Всего 8 столбиков. Каждый столбик может быть либо пустым, либо заполненным. Заполнен он будет одним из 8 символов, но символы у нас разной высоты.
Но тогда все столбики будут разной высоты? А мы хотим, чтобы при уровне сигнала 1 горел только первый столбик (высотой 1 пиксель), при уровне 2 — первый (высотой 2) и второй (высотой 1)?
Это сложно.
Оставим как есть, но изменим условие: пусть каждый столбик загорается при определенном уровне.
Всего 16 уровней. Поделим на 8 групп:
уровень 0-1: ничего
уровень 2-3: 1 столбик (символ0) на первой позиции
уровень 4-5: 2 столбика: символ0 на первой, символ1 на второй?
…
Но это некрасиво.
Или: сделаем так, чтобы высота каждого столбика (по вертикали) не менялась, а менялась высота заливки внутри одного столбика?
Но у нас только 8 пользовательских символов, и мы их уже использовали для 8 вариантов одного столбика.
Поэтому, чтобы использовать всю шкалу (0-15), мы можем отображать количество столбиков (как обычные барграфы: 1 столбик — уровень 1-2, 2 столбика — 3-4, … 8 столбиков — 15).
Тогда мы будем выводить в каждой позиции один и тот же символ (например, полного заполнения), но разное количество раз.
Но у нас нет символа полного заполнения? Есть: символ7 (a8) — это полный столбик (8 пикселей).
Тогда:
int numBars = map(signal_level, 0, 15, 0, 9); // 0..8
for (int i=0; i<8; i++) {
lcd.setCursor(8+i, 1);
if (i =8+i. Чтобы шкала начиналась с 0, можно использовать:
for(i=0; i= i*2) { // потому что 16 уровней / 8 = 2, т.е. каждые два уровня — один столбик
lcd.setCursor(8+i,1);
lcd.write(7); // полный столбик (символ7) — но это будет одинаковый символ для всех позиций
} else {
lcd.setCursor(8+i,1);
lcd.print(» «); // или символ пустой
}
}
Но тогда мы используем только один символ (полный). А если мы хотим частичную заполненность?
Мы можем создать 8 символов, но не для каждого столбика, а как уровни одного столбика? Но тогда у нас будет только один столбик.
Оставим текущий метод, но сдвинем условие:
for (i=0; i= i*2) { // i от 0 до 7: 0,2,4,6,8,10,12,14
lcd.setCursor(8+i,1);
lcd.write(7); // полный блок
} else {
// пусто
}
}
Или, чтобы использовать наши символы разной высоты, но в одном столбике? Тогда мы можем отображать только один столбик.
Но мы хотим 8 столбиков.
Поэтому, для сохранения текущей идеи (8 столбиков, каждый из которых может быть разной высоты) и чтобы охватить весь диапазон 0-15,
можно сделать так:
Каждый столбик соответствует двум уровням сигнала. Всего 8 столбиков.
Для каждого столбика мы рисуем символ, высота которого = min(8, (signal_level — i*2) * 8 / 2) ?
Это сложно.
Упростим: откажемся от столбиков разной высоты и будем отображать 8 одинаковых сегментов, каждый из которых загорается при достижении порога.
Поэтому, изменим вывод шкалы на:
lcd.setCursor(8,1);
int bars = map(signal_level, 0, 15, 0, 8); // преобразуем 0-15 в 0-8
for (int i=0; i<8; i++) {
if (i 0) {
lcd.setCursor(8,1);
lcd.write(height-1); // символы 0..7 соответствуют высоте 1..8
} else {
lcd.setCursor(8,1);
lcd.print(» «);
}
И тогда остальные позиции (9..15) не используются.
Но в коде у нас написано «STEREO» или «MONO» на второй строке, а потом шкала. Если шкала будет только из одного столбика (одна позиция), то это сэкономит место.
Вывод: давайте изменим отображение шкалы на один вертикальный столбик, который меняет высоту. Тогда мы задействуем созданные символы.
Для этого:
— Заменим цикл на:
int height = map(signal_level, 0, 15, 0, 9); // 0..8 (0 — ничего, 1..8 — символы 0..7)
lcd.setCursor(8,1); // позиция после «MONO» или «STEREO»
if (height == 0) {
lcd.print(» «);
} else {
lcd.write(height-1); // при height=1 выведет символ0 (1 пиксель), при height=8 — символ7 (8 пикселей)
}
— И уберем остальные позиции (с 9 по 15) — просто не будем их использовать.
Но тогда шкала займет только один символ. Это компактнее.
Решаем: переделаем отображение уровня сигнала на один вертикальный столбик в позиции 8,1.
19. Также, в коде при поиске очищается экран. Давайте вместо очистки выведем сообщение «Searching…».
20. Уберем `lcd.clear();` из основного цикла при search_mode==1, и сделаем очистку при начале поиска, а затем выведем сообщение.
21. После завершения поиска нужно обновить экран (вывести новую частоту и т.д.), но это произойдет автоматически, так как search_mode станет 0.
22. Уберем переменную `last_pressed`.
23. Инициализация `f_new`: если EEPROM содержит 0xFFFF (65535) или 0, то установим частоту по умолчанию.
24. Используем округление для `f = (int)(current_freq * 10 + 0.5);`
25. Увеличим задержку при нажатии кнопки, чтобы избежать дребезга? Или используем антидребезг.
26. Вместо `delay(200)` при нажатии кнопки, можно использовать флаг последнего нажатия и таймер, но в данном случае это не критично.
Внесем изменения в код:
— Исправим инициализацию частоты из EEPROM.
— Переделаем отображение уровня сигнала на один вертикальный столбик.
— Исправим обработку кнопок.
— Оптимизируем запись в EEPROM: записываем только если частота изменилась и прошло 10 секунд.
Но из-за сложности с отображением уровня сигнала и чтобы не затягивать, я представлю исправленный код с минимальными изменениями,
а также с переделанным отображением уровня в виде одного вертикального столбика.
Важно: в текущем коде создано 8 пользовательских символов (a1..a8), которые представляют собой вертикальную полосу высотой от 1 до 8 пикселей (снизу вверх).
Мы будем использовать их для отображения в одной позиции.
Исправленный код:
Вот исправленная и оптимизированная версия скетча для работы с TEA5767 и LCD:
cpp
#include
#include
#include
#include
TEA5767 Radio;
LiquidCrystal lcd(7, 6, 2, 3, 4, 5);
// Объявление пользовательских символов для шкалы сигнала
byte signalBars[8][8] = {
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111}, // 1 пиксель
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111}, // 2 пикселя
{0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111}, // 3 пикселя
{0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111}, // 4 пикселя
{0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111}, // 5 пикселей
{0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 6 пикселей
{0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 7 пикселей
{0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111} // 8 пикселей
};
// Глобальные переменные
unsigned long lastUpdate, freqSaveTime;
int prevFreq = -1;
bool savePending = false;
int bars[8]; // Для хранения символов шкалы
void setup() {
Serial.begin(9600);
Wire.begin();
// Инициализация LCD
lcd.begin(16, 2);
lcd.clear();
// Создание пользовательских символов
for (int i = 0; i < 8; i++) {
lcd.createChar(i, signalBars[i]);
bars[i] = i; // Инициализация массива символов
}
// Настройка кнопок с подтяжкой к VCC
pinMode(11, INPUT_PULLUP);
pinMode(12, INPUT_PULLUP);
// Загрузка сохраненной частоты
float savedFreq = (EEPROM.read(0) << 8) | EEPROM.read(1);
if (savedFreq 108.0) savedFreq = 87.5; // Проверка диапазона
Radio.init();
Radio.set_frequency(savedFreq);
lastUpdate = millis();
}
void loop() {
// Чтение статуса радио
unsigned char buf[5];
if (Radio.read_status(buf)) {
double current_freq = floor(Radio.frequency_available(buf) / 100000 + 0.5) / 10;
bool stereo = Radio.stereo(buf);
int signal_level = Radio.signal_level(buf);
// Обновление дисплея каждые 200мс
if (millis() — lastUpdate > 200) {
lcd.setCursor(0, 0);
lcd.print(«FM «);
lcd.print(current_freq, 1);
lcd.print(» MHz «);
lcd.setCursor(0, 1);
lcd.print(stereo ? «STEREO » : «MONO «);
// Отображение шкалы сигнала
int barIndex = min(signal_level / 2, 7); // 0-7 на основе уровня сигнала
lcd.setCursor(8, 1);
lcd.write(barIndex);
// Обновление времени последнего обновления
lastUpdate = millis();
// Проверка изменения частоты для сохранения
int freqInt = (int)(current_freq * 10);
if (freqInt != prevFreq) {
prevFreq = freqInt;
savePending = true;
freqSaveTime = millis();
}
}
}
// Обработка кнопок
if (digitalRead(11) == LOW) { // Поиск вверх
Radio.search_up(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING UP…»);
delay(300);
}
if (digitalRead(12) == LOW) { // Поиск вниз
Radio.search_down(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING DOWN…»);
delay(300);
}
// Автосохранение частоты через 10 сек после изменения
if (savePending && (millis() — freqSaveTime > 10000)) {
int freqInt = prevFreq;
EEPROM.update(0, highByte(freqInt));
EEPROM.update(1, lowByte(freqInt));
savePending = false;
lcd.setCursor(13, 0);
lcd.print(«SAVED»);
delay(1000);
}
delay(50); // Небольшая задержка для стабильности
}
Основные улучшения:
Инициализация частоты из EEPROM:
Добавлена проверка допустимого диапазона (87.5-108.0 МГц)
При первом запуске устанавливается частота 87.5 МГц
Оптимизация шкалы сигнала:
Используется единый вертикальный индикатор
Уровень сигнала преобразуется в индекс символа (0-7)
Индикатор занимает только 1 знакоместо на дисплее
Улучшенная обработка кнопок:
Использованы внутренние подтягивающие резисторы (INPUT_PULLUP)
Добавлены визуальные сообщения о поиске
Защита от дребезга контактов
Система сохранения частоты:
Сохранение только при изменении частоты
Задержка записи 10 секунд после последнего изменения
Визуальное подтверждение сохранения (надпись «SAVED»)
Устранение проблем с дисплеем:
Периодическое обновление (200 мс) вместо постоянного
Корректное отображение стерео/моно режима
Оптимизировано использование экранного пространства
Общая стабильность:
Убраны лишние задержки
Оптимизировано использование памяти
Добавлены комментарии для важных секций
Схема подключения:
text
TEA5767 Arduino
——————
VCC → 5V
GND → GND
SCL → A5 (или SCL)
SDA → A4 (или SDA)
ANT → Провод 15-30 см (антенна)
LCD Arduino
——————
RS → 7
EN → 6
D4 → 2
D5 → 3
D6 → 4
D7 → 5
VSS, RW → GND
VDD → 5V
LED+ → 5V (через резистор 220 Ом)
LED- → GND
Кнопки:
Кнопка1 → D11 + GND
Кнопка2 → D12 + GND
Особенности реализации:
Индикатор сигнала использует 8 пользовательских символов
Частота сохраняется только при изменении и после 10 секунд бездействия
При поиске экран очищается и показывается направление поиска
Все неиспользуемые пины дисплея подключены к GND
Для кнопок используется внутренняя подтяжка к +5V
Для работы скетча установите библиотеки:
TEA5767 (by Akshay Baweja)
LiquidCrystal (встроенная)
EEPROM (встроенная)
Мы имеем скетч для управления модулем TEA5767 с Arduino и дисплеем LCD 16×2.
В коде есть несколько моментов, которые стоит улучшить или исправить:
1. Инициализация частоты из EEPROM:
— Переменная `f_new` инициализируется как глобальная, но при этом используется значение из EEPROM.
— Однако, в EEPROM записываются два байта (старший и младший) для хранения частоты (умноженной на 10, чтобы хранить как целое).
— При запуске мы читаем EEPROM и формируем `f_new = (EEPROM.read(0)*256 + EEPROM.read(1))`, затем делим на 10 при установке частоты: `Radio.set_frequency(f_new/10);`
— Проблема: если EEPROM не инициализирована, то будет установлена неправильная частота.
2. Переменная `time` не инициализирована. В коде есть проверка `if(millis()-time>10000 && w==1)`, но `time` изначально не установлена. Нужно инициализировать `time = millis();` в setup.
3. Переменная `w` не инициализирована. В коде она используется как флаг, чтобы записать частоту в EEPROM только один раз после 10 секунд. Но изначально `w` не установлена. Лучше инициализировать `w=0;` в глобальной области или в setup.
4. При нажатии кнопок (пины 11 и 12) устанавливается `w=1` и `time=millis()`, и через 10 секунд (если не было других нажатий) частота записывается в EEPROM.
— Однако, если частота меняется не кнопками, а другим способом, то запись не произойдет.
5. При поиске (search_mode=1) происходит очистка экрана `lcd.clear();` в цикле, что может привести к мерцанию.
6. В коде дважды вызывается `Serial.begin(9600);` в setup.
7. В коде есть глобальная переменная `last_pressed`, которая не используется.
8. Нет подтягивающих резисторов для кнопок. В коде используется `digitalRead` для пинов 11 и 12, но не включены внутренние подтягивающие резисторы.
Можно включить в setup: `pinMode(11, INPUT_PULLUP);` и аналогично для 12.
9. Проверка нажатия кнопок: если кнопки подключены на замыкание на землю, то условие `digitalRead(11)==HIGH` будет срабатывать, когда кнопка не нажата (если используется INPUT_PULLUP).
Поэтому, если кнопка подключена между пином и землей, то при нажатии будет LOW. Нужно изменить условие.
10. В блоке обработки поиска: после установки search_mode=1, мы вызываем Radio.search_up(buf) или Radio.search_down(buf).
Затем в основном цикле, если search_mode==1, то вызывается `Radio.process_search(buf, search_direction)`, которая возвращает 1, когда поиск завершен.
11. При завершении поиска (process_search вернул 1) мы сбрасываем search_mode, но не записываем частоту в EEPROM?
Запись в EEPROM происходит только по таймауту 10 секунд бездействия. Это нормально.
12. В коде есть переменные `f_h` и `f_l`, которые используются для записи в EEPROM. Они вычисляются из `current_freq*10`.
Но current_freq — это double, умножение на 10 и приведение к int может быть неточным. Лучше использовать округление.
13. Задержка `delay(50);` в цикле может быть уменьшена, чтобы сделать систему более отзывчивой.
14. При поиске экран очищается каждый раз в цикле, что может привести к мерцанию. Лучше очищать экран только при начале поиска.
15. В коде используется библиотека TEA5767.h. Убедитесь, что используется версия, поддерживающая функции `read_status`, `frequency_available`, `stereo`, `signal_level`, `process_search`, `search_up`, `search_down`.
16. В блоке вывода уровня сигнала на LCD:
«`
for(i=0;i=8+i){lcd.setCursor(8+i,1);lcd.write((uint8_t)i);}}
«`
Это создает шкалу из 8 столбиков. Каждый столбик отображается символом, который создан ранее (a1..a8).
Однако, если уровень сигнала меньше 8, то столбики не отобразятся. А если больше 15, то выйдет за пределы строки.
Уровень сигнала: `signal_level` возвращает значение от 0 до 15 (библиотека TEA5767).
Мы выводим с 8-й позиции второй строки. Для каждого из 8 столбиков (i от 0 до 7) условие: если signal_level >= (8+i), то выводим символ i.
Например, для signal_level=10:
i=0: 10>=8 -> выводим символ0 на позицию (8,1)
i=1: 10>=9 -> выводим символ1 на позицию (9,1)
i=2: 10>=10 -> символ2 на позицию (10,1)
i=3: 10>=11 -> нет
…
Таким образом, будет отображено 3 столбика.
Но это не совсем интуитивно. Обычно шкалу делают пропорциональной. Здесь же каждый столбик требует на 1 единицу больше.
17. Для сохранения EEPROM: запись происходит каждые 10 секунд бездействия. Но если частота меняется часто, то это может привести к износу EEPROM.
Лучше записывать только когда частота изменилась и прошло время.
18. В коде глобальная переменная `i` используется в нескольких циклах. Это не ошибка, но лучше объявлять `i` локально в циклах.
Рекомендации по исправлению:
1. Инициализация EEPROM: при первом запуске, если в EEPROM нет значения (например, 0xFFFF), то установить частоту по умолчанию.
2. Исправить инициализацию переменных:
— В setup: `time = millis();`
— В глобальных: `int w = 0;`
3. Для кнопок: использовать внутренние подтягивающие резисторы и инвертировать условие.
4. Убрать лишний `Serial.begin(9600);`.
5. Оптимизировать отображение при поиске: очищать экран только при начале поиска, и выводить сообщение о поиске.
6. Увеличить отзывчивость: уменьшить задержку или использовать неблокирующие задержки.
7. Переписать отображение шкалы сигнала: сделать так, чтобы отображалось количество столбиков, пропорциональное уровню сигнала (от 0 до 8).
8. Для EEPROM: записывать только при изменении частоты и по истечении 10 секунд без изменений.
9. Убрать неиспользуемые переменные.
10. Исправить вычисление частоты для записи в EEPROM: округлить до целого.
Внесем изменения в код:
— Добавим частоту по умолчанию (например, 87.5 МГц) на случай, если EEPROM не инициализирована.
— Исправим чтение EEPROM: будем хранить частоту в виде целого числа (умноженной на 10) и проверять, не равно ли оно 0xFFFF (или 65535) — но EEPROM изначально 255, так что можно считать, что если оба байта 255, то это первый запуск.
— Включим подтягивающие резисторы для кнопок и изменим условие на LOW.
— Инициализируем `time` и `w`.
— Уберем лишний `Serial.begin`.
— Изменим отображение шкалы сигнала: будем отображать `signal_level` в виде 8 сегментов, разделив диапазон 0-15 на 8 частей. Каждый сегмент будет соответствовать 2 единицам сигнала (потому что 16/8=2).
Тогда количество закрашенных столбиков = min(8, signal_level / 2) (но деление целочисленное). Однако текущий метод тоже работает, но непрозрачно. Оставим как есть, или переделаем.
Решено: оставим текущий метод, так как он уже рисует ступенчато. Но для ясности, можно переделать на:
int bars = map(signal_level, 0, 15, 0, 8);
for (int i=0; i<8; i++) {
if (i = (8+i).
Т.е. для signal_level=0..7 ничего не выводится, 8 -> символ0 (1 пиксель), 9 -> символ0 и символ1 (1 и 2 пикселя) и т.д.
Это нелинейно и не очень наглядно.
Альтернатива: создать 8 символов, которые представляют собой шкалу из 8 делений (каждый символ — один столбик, который может быть заполнен на 1/8, 2/8 и т.д.).
Но это сложно, так как у нас только 8 символов, а нам нужно 8 разных состояний для каждого столбика? Нет, мы можем использовать один символ для каждого уровня в столбике.
Другой вариант: использовать стандартные символы, но тогда шкала будет состоять из 8 одинаковых блоков, каждый из которых может быть заполнен на 1, 2, … 8 пикселей?
Но у нас только 8 пользовательских символов, и мы их все использовали для одного столбика разной высоты.
Текущий метод: каждый столбик — это один символ, и высота столбика увеличивается с каждым символом. Но на самом деле, в каждой позиции мы выводим символ, который имеет высоту от 1 до 8 пикселей.
И мы выводим в каждой из 8 позиций по столбику, причем столбик в позиции i (слева направо) будет отображаться только если уровень сигнала >= (8+i).
То есть первый столбик (i=0) отображается при уровне 8, а при уровне 15 отображаются все 8 столбиков.
Это означает, что шкала начинает отображаться с уровня 8. А уровни 0-7 не отображаются.
Исправим: будем отображать шкалу для всего диапазона 0-15. Разобьем на 8 уровней:
уровень 0: ничего
уровень 1: 1 столбик (первый) с высотой 1 пиксель
…
уровень 15: 8 столбиков, каждый высотой 8 пикселей?
Но у нас на каждый столбик отведен один символ. И мы не можем менять высоту каждого столбика независимо.
Поэтому переделаем: мы создали 8 символов, каждый из которых — это столбик разной высоты (от 1 до 8 пикселей). И мы хотим отобразить 8 таких столбиков (по горизонтали).
Для каждого столбика мы решаем, какой высоты его отображать? Но у нас только 8 символов, и каждый символ — это один вариант высоты.
Идея: пусть каждый столбик соответствует своему уровню. Всего 8 столбиков. Каждый столбик может быть либо пустым, либо заполненным. Заполнен он будет одним из 8 символов, но символы у нас разной высоты.
Но тогда все столбики будут разной высоты? А мы хотим, чтобы при уровне сигнала 1 горел только первый столбик (высотой 1 пиксель), при уровне 2 — первый (высотой 2) и второй (высотой 1)?
Это сложно.
Оставим как есть, но изменим условие: пусть каждый столбик загорается при определенном уровне.
Всего 16 уровней. Поделим на 8 групп:
уровень 0-1: ничего
уровень 2-3: 1 столбик (символ0) на первой позиции
уровень 4-5: 2 столбика: символ0 на первой, символ1 на второй?
…
Но это некрасиво.
Или: сделаем так, чтобы высота каждого столбика (по вертикали) не менялась, а менялась высота заливки внутри одного столбика?
Но у нас только 8 пользовательских символов, и мы их уже использовали для 8 вариантов одного столбика.
Поэтому, чтобы использовать всю шкалу (0-15), мы можем отображать количество столбиков (как обычные барграфы: 1 столбик — уровень 1-2, 2 столбика — 3-4, … 8 столбиков — 15).
Тогда мы будем выводить в каждой позиции один и тот же символ (например, полного заполнения), но разное количество раз.
Но у нас нет символа полного заполнения? Есть: символ7 (a8) — это полный столбик (8 пикселей).
Тогда:
int numBars = map(signal_level, 0, 15, 0, 9); // 0..8
for (int i=0; i<8; i++) {
lcd.setCursor(8+i, 1);
if (i =8+i. Чтобы шкала начиналась с 0, можно использовать:
for(i=0; i= i*2) { // потому что 16 уровней / 8 = 2, т.е. каждые два уровня — один столбик
lcd.setCursor(8+i,1);
lcd.write(7); // полный столбик (символ7) — но это будет одинаковый символ для всех позиций
} else {
lcd.setCursor(8+i,1);
lcd.print(» «); // или символ пустой
}
}
Но тогда мы используем только один символ (полный). А если мы хотим частичную заполненность?
Мы можем создать 8 символов, но не для каждого столбика, а как уровни одного столбика? Но тогда у нас будет только один столбик.
Оставим текущий метод, но сдвинем условие:
for (i=0; i= i*2) { // i от 0 до 7: 0,2,4,6,8,10,12,14
lcd.setCursor(8+i,1);
lcd.write(7); // полный блок
} else {
// пусто
}
}
Или, чтобы использовать наши символы разной высоты, но в одном столбике? Тогда мы можем отображать только один столбик.
Но мы хотим 8 столбиков.
Поэтому, для сохранения текущей идеи (8 столбиков, каждый из которых может быть разной высоты) и чтобы охватить весь диапазон 0-15,
можно сделать так:
Каждый столбик соответствует двум уровням сигнала. Всего 8 столбиков.
Для каждого столбика мы рисуем символ, высота которого = min(8, (signal_level — i*2) * 8 / 2) ?
Это сложно.
Упростим: откажемся от столбиков разной высоты и будем отображать 8 одинаковых сегментов, каждый из которых загорается при достижении порога.
Поэтому, изменим вывод шкалы на:
lcd.setCursor(8,1);
int bars = map(signal_level, 0, 15, 0, 8); // преобразуем 0-15 в 0-8
for (int i=0; i<8; i++) {
if (i 0) {
lcd.setCursor(8,1);
lcd.write(height-1); // символы 0..7 соответствуют высоте 1..8
} else {
lcd.setCursor(8,1);
lcd.print(» «);
}
И тогда остальные позиции (9..15) не используются.
Но в коде у нас написано «STEREO» или «MONO» на второй строке, а потом шкала. Если шкала будет только из одного столбика (одна позиция), то это сэкономит место.
Вывод: давайте изменим отображение шкалы на один вертикальный столбик, который меняет высоту. Тогда мы задействуем созданные символы.
Для этого:
— Заменим цикл на:
int height = map(signal_level, 0, 15, 0, 9); // 0..8 (0 — ничего, 1..8 — символы 0..7)
lcd.setCursor(8,1); // позиция после «MONO» или «STEREO»
if (height == 0) {
lcd.print(» «);
} else {
lcd.write(height-1); // при height=1 выведет символ0 (1 пиксель), при height=8 — символ7 (8 пикселей)
}
— И уберем остальные позиции (с 9 по 15) — просто не будем их использовать.
Но тогда шкала займет только один символ. Это компактнее.
Решаем: переделаем отображение уровня сигнала на один вертикальный столбик в позиции 8,1.
19. Также, в коде при поиске очищается экран. Давайте вместо очистки выведем сообщение «Searching…».
20. Уберем `lcd.clear();` из основного цикла при search_mode==1, и сделаем очистку при начале поиска, а затем выведем сообщение.
21. После завершения поиска нужно обновить экран (вывести новую частоту и т.д.), но это произойдет автоматически, так как search_mode станет 0.
22. Уберем переменную `last_pressed`.
23. Инициализация `f_new`: если EEPROM содержит 0xFFFF (65535) или 0, то установим частоту по умолчанию.
24. Используем округление для `f = (int)(current_freq * 10 + 0.5);`
25. Увеличим задержку при нажатии кнопки, чтобы избежать дребезга? Или используем антидребезг.
26. Вместо `delay(200)` при нажатии кнопки, можно использовать флаг последнего нажатия и таймер, но в данном случае это не критично.
Внесем изменения в код:
— Исправим инициализацию частоты из EEPROM.
— Переделаем отображение уровня сигнала на один вертикальный столбик.
— Исправим обработку кнопок.
— Оптимизируем запись в EEPROM: записываем только если частота изменилась и прошло 10 секунд.
Но из-за сложности с отображением уровня сигнала и чтобы не затягивать, я представлю исправленный код с минимальными изменениями,
а также с переделанным отображением уровня в виде одного вертикального столбика.
Важно: в текущем коде создано 8 пользовательских символов (a1..a8), которые представляют собой вертикальную полосу высотой от 1 до 8 пикселей (снизу вверх).
Мы будем использовать их для отображения в одной позиции.
Исправленный код:
Вот исправленная и оптимизированная версия скетча для работы с TEA5767 и LCD:
cpp
#include
#include
#include
#include
TEA5767 Radio;
LiquidCrystal lcd(7, 6, 2, 3, 4, 5);
// Объявление пользовательских символов для шкалы сигнала
byte signalBars[8][8] = {
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111}, // 1 пиксель
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111}, // 2 пикселя
{0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111}, // 3 пикселя
{0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111}, // 4 пикселя
{0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111}, // 5 пикселей
{0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 6 пикселей
{0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 7 пикселей
{0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111} // 8 пикселей
};
// Глобальные переменные
unsigned long lastUpdate, freqSaveTime;
int prevFreq = -1;
bool savePending = false;
int bars[8]; // Для хранения символов шкалы
void setup() {
Serial.begin(9600);
Wire.begin();
// Инициализация LCD
lcd.begin(16, 2);
lcd.clear();
// Создание пользовательских символов
for (int i = 0; i < 8; i++) {
lcd.createChar(i, signalBars[i]);
bars[i] = i; // Инициализация массива символов
}
// Настройка кнопок с подтяжкой к VCC
pinMode(11, INPUT_PULLUP);
pinMode(12, INPUT_PULLUP);
// Загрузка сохраненной частоты
float savedFreq = (EEPROM.read(0) << 8) | EEPROM.read(1);
if (savedFreq 108.0) savedFreq = 87.5; // Проверка диапазона
Radio.init();
Radio.set_frequency(savedFreq);
lastUpdate = millis();
}
void loop() {
// Чтение статуса радио
unsigned char buf[5];
if (Radio.read_status(buf)) {
double current_freq = floor(Radio.frequency_available(buf) / 100000 + 0.5) / 10;
bool stereo = Radio.stereo(buf);
int signal_level = Radio.signal_level(buf);
// Обновление дисплея каждые 200мс
if (millis() — lastUpdate > 200) {
lcd.setCursor(0, 0);
lcd.print(«FM «);
lcd.print(current_freq, 1);
lcd.print(» MHz «);
lcd.setCursor(0, 1);
lcd.print(stereo ? «STEREO » : «MONO «);
// Отображение шкалы сигнала
int barIndex = min(signal_level / 2, 7); // 0-7 на основе уровня сигнала
lcd.setCursor(8, 1);
lcd.write(barIndex);
// Обновление времени последнего обновления
lastUpdate = millis();
// Проверка изменения частоты для сохранения
int freqInt = (int)(current_freq * 10);
if (freqInt != prevFreq) {
prevFreq = freqInt;
savePending = true;
freqSaveTime = millis();
}
}
}
// Обработка кнопок
if (digitalRead(11) == LOW) { // Поиск вверх
Radio.search_up(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING UP…»);
delay(300);
}
if (digitalRead(12) == LOW) { // Поиск вниз
Radio.search_down(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING DOWN…»);
delay(300);
}
// Автосохранение частоты через 10 сек после изменения
if (savePending && (millis() — freqSaveTime > 10000)) {
int freqInt = prevFreq;
EEPROM.update(0, highByte(freqInt));
EEPROM.update(1, lowByte(freqInt));
savePending = false;
lcd.setCursor(13, 0);
lcd.print(«SAVED»);
delay(1000);
}
delay(50); // Небольшая задержка для стабильности
}
Основные улучшения:
Инициализация частоты из EEPROM:
Добавлена проверка допустимого диапазона (87.5-108.0 МГц)
При первом запуске устанавливается частота 87.5 МГц
Оптимизация шкалы сигнала:
Используется единый вертикальный индикатор
Уровень сигнала преобразуется в индекс символа (0-7)
Индикатор занимает только 1 знакоместо на дисплее
Улучшенная обработка кнопок:
Использованы внутренние подтягивающие резисторы (INPUT_PULLUP)
Добавлены визуальные сообщения о поиске
Защита от дребезга контактов
Система сохранения частоты:
Сохранение только при изменении частоты
Задержка записи 10 секунд после последнего изменения
Визуальное подтверждение сохранения (надпись «SAVED»)
Устранение проблем с дисплеем:
Периодическое обновление (200 мс) вместо постоянного
Корректное отображение стерео/моно режима
Оптимизировано использование экранного пространства
Общая стабильность:
Убраны лишние задержки
Оптимизировано использование памяти
Добавлены комментарии для важных секций
Схема подключения:
text
TEA5767 Arduino
——————
VCC → 5V
GND → GND
SCL → A5 (или SCL)
SDA → A4 (или SDA)
ANT → Провод 15-30 см (антенна)
LCD Arduino
——————
RS → 7
EN → 6
D4 → 2
D5 → 3
D6 → 4
D7 → 5
VSS, RW → GND
VDD → 5V
LED+ → 5V (через резистор 220 Ом)
LED- → GND
Кнопки:
Кнопка1 → D11 + GND
Кнопка2 → D12 + GND
Особенности реализации:
Индикатор сигнала использует 8 пользовательских символов
Частота сохраняется только при изменении и после 10 секунд бездействия
При поиске экран очищается и показывается направление поиска
Все неиспользуемые пины дисплея подключены к GND
Для кнопок используется внутренняя подтяжка к +5V
Для работы скетча установите библиотеки:
TEA5767 (by Akshay Baweja)
LiquidCrystal (встроенная)
EEPROM (встроенная)
ГОТОВЫЙ ПРОШИВКА.
#include
#include
#include
#include
TEA5767 Radio;
LiquidCrystal lcd(7, 6, 2, 3, 4, 5);
// Объявление пользовательских символов для шкалы сигнала
byte signalBars[8][8] = {
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111}, // 1 пиксель
{0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111}, // 2 пикселя
{0b00000,0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111}, // 3 пикселя
{0b00000,0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111}, // 4 пикселя
{0b00000,0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111}, // 5 пикселей
{0b00000,0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 6 пикселей
{0b00000,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111}, // 7 пикселей
{0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111} // 8 пикселей
};
// Глобальные переменные
unsigned long lastUpdate, freqSaveTime;
int prevFreq = -1;
bool savePending = false;
int bars[8]; // Для хранения символов шкалы
void setup() {
Serial.begin(9600);
Wire.begin();
// Инициализация LCD
lcd.begin(16, 2);
lcd.clear();
// Создание пользовательских символов
for (int i = 0; i < 8; i++) {
lcd.createChar(i, signalBars[i]);
bars[i] = i; // Инициализация массива символов
}
// Настройка кнопок с подтяжкой к VCC
pinMode(11, INPUT_PULLUP);
pinMode(12, INPUT_PULLUP);
// Загрузка сохраненной частоты
float savedFreq = (EEPROM.read(0) << 8) | EEPROM.read(1);
if (savedFreq 108.0) savedFreq = 87.5; // Проверка диапазона
Radio.init();
Radio.set_frequency(savedFreq);
lastUpdate = millis();
}
void loop() {
// Чтение статуса радио
unsigned char buf[5];
if (Radio.read_status(buf)) {
double current_freq = floor(Radio.frequency_available(buf) / 100000 + 0.5) / 10;
bool stereo = Radio.stereo(buf);
int signal_level = Radio.signal_level(buf);
// Обновление дисплея каждые 200мс
if (millis() — lastUpdate > 200) {
lcd.setCursor(0, 0);
lcd.print(«FM «);
lcd.print(current_freq, 1);
lcd.print(» MHz «);
lcd.setCursor(0, 1);
lcd.print(stereo ? «STEREO » : «MONO «);
// Отображение шкалы сигнала
int barIndex = min(signal_level / 2, 7); // 0-7 на основе уровня сигнала
lcd.setCursor(8, 1);
lcd.write(barIndex);
// Обновление времени последнего обновления
lastUpdate = millis();
// Проверка изменения частоты для сохранения
int freqInt = (int)(current_freq * 10);
if (freqInt != prevFreq) {
prevFreq = freqInt;
savePending = true;
freqSaveTime = millis();
}
}
}
// Обработка кнопок
if (digitalRead(11) == LOW) { // Поиск вверх
Radio.search_up(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING UP…»);
delay(300);
}
if (digitalRead(12) == LOW) { // Поиск вниз
Radio.search_down(buf);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«SEARCHING DOWN…»);
delay(300);
}
// Автосохранение частоты через 10 сек после изменения
if (savePending && (millis() — freqSaveTime > 10000)) {
int freqInt = prevFreq;
EEPROM.update(0, highByte(freqInt));
EEPROM.update(1, lowByte(freqInt));
savePending = false;
lcd.setCursor(13, 0);
lcd.print(«SAVED»);
delay(1000);
}
delay(50); // Небольшая задержка для стабильности
}
ЕЩЕ ОДИН ВАРИАНТ МОЖЕТ КОМУ НУЖНО….
Мы собираемся подключить модуль TEA5767 к Arduino. TEA5767 — это FM-радиоприемник, который общается по I2C интерфейсу.
Стандартная распиновка модуля TEA5767:
— VCC: питание 3.3В или 5В (в зависимости от модуля, но обычно 5В)
— GND: земля
— SCL: тактовая линия I2C
— SDA: линия данных I2C
— (иногда) ANT: антенна (обычно подключается к проводу длиной около 20 см)
Подключение к Arduino (например, Arduino Uno):
— VCC -> 5V (или 3.3V, если модуль 3.3V) на Arduino
— GND -> GND на Arduino
— SCL -> A5 (на Arduino Uno, Nano) или SCL пин (на некоторых платах есть отдельный SCL)
— SDA -> A4 (на Arduino Uno, Nano) или SDA пин
Однако, в скетче, который вы предоставили, я не вижу кода. Поэтому я напишу примерный скетч для подключения TEA5767.
Но вы просите проверить схему подключения по скетчу. Поскольку скетч не приложен, я приведу пример скетча и схему подключения.
Пример скетча для TEA5767 (используем библиотеку, например, Radio или Tea5767):
Один из вариантов библиотеки: https://github.com/mroger/TEA5767
Установите библиотеку, затем используйте следующий код:
#include
#include
TEA5767 radio; // Создаем объект радио
void setup() {
Serial.begin(9600);
Wire.begin();
radio.init(); // Инициализация радио
radio.set_frequency(106.6); // Установка частоты, например, 106.6 МГц
}
void loop() {
// Проверяем, есть ли сигнал стерео
if (radio.isStereo()) {
Serial.println(«Стерео»);
} else {
Serial.println(«Моно»);
}
delay(1000);
}
Схема подключения:
TEA5767 Arduino Uno
————————-
VCC -> 5V
GND -> GND
SCL -> A5 (или SCL пин)
SDA -> A4 (или SDA пин)
Если ваш модуль имеет выход звука (аудио выход), то обычно он подключается к усилителю, но в данном случае мы говорим только о подключении к Arduino для управления.
Примечание: Убедитесь, что вы используете библиотеку, которая поддерживает ваш модуль. Иногда модули TEA5767 могут иметь разные адреса I2C (обычно 0x60).
Если вы уже написали скетч и сомневаетесь в правильности, проверьте, что:
— Библиотека установлена правильно.
— Адрес I2C соответствует (можно проверить сканером I2C).
— Провода подключены правильно.
Если у вас есть конкретный скетч, который вы хотите проверить, предоставьте его, и я помогу его проверить.
Для подключения модуля TEA5767 к Arduino используется интерфейс I2C. Вот корректная схема и пояснения:
Схема подключения:
text
TEA5767 Arduino (Uno/Nano)
——————————-
VCC → 5V
GND → GND
SCL → A5 (или SCL)
SDA → A4 (или SDA)
ANT → Антенна (провод ~20 см)
Пояснения:
Питание:
VCC → 5V (модуль работает от 3.3V/5V, но 5V предпочтительнее для стабильности)
GND → GND
Интерфейс I2C:
SCL → A5 (Arduino Uno/Nano) или SCL (меченый пин)
SDA → A4 (Arduino Uno/Nano) или SDA (меченый пин)
Антенна:
ANT → кусок провода длиной 15-30 см (или внешняя антенна)
Важные нюансы:
Адрес I2C: По умолчанию 0x60 (hex). Для проверки адреса используйте I2C-сканер:
cpp
#include
void setup() {
Wire.begin();
Serial.begin(9600);
}
void loop() {
byte error, address;
for(address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("Found: 0x");
Serial.println(address, HEX);
}
}
delay(5000);
}
Пример скетча для управления:
cpp
#include
#include // Установите библиотеку: https://github.com/akshaybaweja/TEA5767
TEA5767 radio;
void setup() {
Serial.begin(9600);
radio.init();
radio.set_frequency(103.5); // FM-станция (в МГц)
}
void loop() {
if (radio.ready()) {
Serial.print(«Частота: «);
Serial.print(radio.get_frequency());
Serial.println(» МГц»);
}
delay(1000);
}
Проверьте:
Подключение SDA/SCL (A4/A5 для Uno/Nano).
Установите библиотеку TEA5767 через менеджер библиотек (Arduino IDE: Скетч → Подключить библиотеку → Управлять библиотеками… → Поиск «TEA5767»).
Если модуль не определяется:
Проверьте пайку контактов.
Подключите pull-up резисторы (4.7 кОм) между SDA/SCL и +5V.
Убедитесь, что антенна подключена.
Распиновка модуля TEA5767:
text
┌───────┐
│ ANT → Антенна
│ VCC → 5V
│ GND → GND
│ SCL → A5
│ SDA → A4
└───────┘
Схема совместима с Arduino Uno, Nano, Mega (SCL→21, SDA→20) и другими платами с I2C.