Загрузка данных
Вставленный текст.txt
Документ
Привет у меня есть такой проект это стенд на котором 13 сенсоров представляющих из себя кнопки и светодиоды рядом с сенсорами и пару отдельных так вот сейчас такая проблема при нажатии на обучение должна проиграться озвучка а ее нет сразу скажу функции отправляющие данные на плеер считаем верной т.к в целом плеер работает если нажать обучение потом дважды пауза озвучка включится но криво найди ошибки пожалуйста
Да. Теперь по симптомам я бы перестал копать play_active и паузу как основную причину.
То, что ты описываешь:
нажимаю «Обучение» → нажимаю любой сенсор → тишина → нажимаю паузу → ещё раз паузу → озвучка начинает играть
очень характерно для того, что команда запуска трека до плеера доходит не в том состоянии/формате, а r от второй паузы фактически запускает уже ожидающий трек.
Я посмотрел именно тот stand_fixed.c, который ты залил. В нём есть важный момент: PLAY() отправляет FF + 3 цифры, но после этого мы сами выставляем play_active = 1.
При этом обработка паузы отправляет:
'p'
при первом нажатии и:
'r'
при втором.
То есть твой симптом буквально может означать:
PLAY(1)
↓
плеер получил/подготовил трек
↓
не начал воспроизведение
↓
первая пауза → p
↓
вторая пауза → r
↓
плеер наконец начинает воспроизведение
Поэтому сейчас мне нужен один очень важный кусок
Пришли мне именно код плеера, который принимает FF + цифры, p, r, s и отправляет @.
Вот его особенно важно увидеть. Стенд мы уже достаточно разобрали, а теперь проблема, скорее всего, на границе:
стенд → UART → контроллер плеера.
Если код плеера у тебя отдельным .c/.cpp, .h или проектом — загрузи его целиком.
И ещё
Пока не надо снова прошивать очередную версию моего кода. Сначала посмотрим приёмную сторону.
Особенно хочу найти в плеере:
обработчик 0xFF;
обработку трёх цифр;
обработку 'p';
обработку 'r';
обработку 's';
место, где реально запускается play;
место, где отправляется @.
Потому что если окажется, что r у плеера не «resume», а, например, повторно запускает подготовленный трек, мы сразу получим объяснение твоего симптома.
И тогда я дам тебе уже одну финальную версию обоих кодов — стенд + плеер, согласованных между собой.
Реклама
Реклама не влияет на ответы, которые вы получаете от ChatGPT. Ваши чаты остаются конфиденциальными.
Подробнее о рекламе
Вставленный текст(1).txt
Документ
Вот код плеера
Ага. Теперь причина стала намного яснее. Я посмотрел код плеера, и нашёл очень важную вещь, которую в прошлый раз мы упустили.
Проблема, судя по коду, находится не в STUDY() как таковом.
Что происходит на самом деле
Плеер получает от стенда последовательность:
FF 2 0 1
для PLAY(201).
Но плеер не запускает трек непосредственно в обработчике UART. Он только разбирает пакет, записывает номер трека и выставляет:
ch_tr = 1;
А реальное:
DFPlayer_Play_track_UserFolder(...)
выполняется уже в основном FreeRTOS-потоке. Это видно в коде плеера: после получения трёх байтов он устанавливает ch_tr = 1, а затем отдельная задача запускает воспроизведение.
И вот тут возникает гонка команд.
Самое интересное — r
Плеер обрабатывает r так:
if (MainBoard_Rx[0] == 'r')
{
DFPlayer_Play();
DFPlayer1.DF_LED = 0x01;
LED_BUSY(1);
}
То есть r действительно запускает воспроизведение текущего трека.
Поэтому твой симптом:
обучение → тишина → два раза пауза → появляется звук
очень похож на следующее:
PLAY(201)
↓
плеер получает номер 201
↓
записывает его как текущий трек
↓
ch_tr = 1
↓
...
r
↓
DFPlayer_Play()
↓
звук появляется
То есть вторая пауза фактически запускает уже выбранный трек.
И вот где я вижу главную ошибку стенда
В твоём AVR-коде PLAY() отправляет четыре байта практически подряд:
UDR = 0xff;
UDR = a + 48;
UDR = b + 48;
UDR = c + 48;
А мы раньше считали, что раз PLAY() в других режимах работает, значит с передачей всё хорошо.
Я теперь не хочу это предполагать.
Плеер ожидает строго:
FF
<3 байта номера>
и только после получения третьего байта устанавливает ch_tr = 1.
Поэтому я предлагаю сейчас сделать правильную вещь:
Не переписывать ещё раз только STUDY().
Нужно переделать весь обмен AVR ↔ STM32, чтобы:
каждый байт UART реально дождался окончания передачи;
после FF + XXX стенд немного ждал;
плеер успевал обработать ch_tr;
p/r не смешивались с командой выбора трека;
@ использовался только как уведомление об окончании;
PLAY() больше не мог терять байты;
после PLAY(201) звук должен стартовать без единого нажатия паузы.
Причём я бы сделал ещё надёжнее
На стороне AVR сделать:
UART_SendByte(0xFF);
UART_SendByte(...);
UART_SendByte(...);
UART_SendByte(...);
где каждый UART_SendByte() ждёт:
while (!(UCSRA & (1 << UDRE)));
И не использовать напрямую UDR = ... вообще для команд плееру.
На стороне STM32 оставить существующий протокол FF + 3 цифры, p, r, s, поскольку он у тебя уже реализован. Плеер также действительно отправляет @, когда получает событие окончания трека.
Я теперь могу сделать тебе нормальную финальную пару файлов:
stand.c — полностью исправленный AVR;
плеер — исправленный STM32 код;
единый надёжный протокол обмена;
обучение запускается сразу;
любой сенсор сразу запускает соответствующую озвучку;
pause работает p → r;
окончание трека приходит как @;
stop работает как s.
И я бы уже не оставлял старую логику play_active == 1/2, а сделал нормальные состояния STOPPED / PLAYING / PAUSED / FINISHED.
Если у тебя исходники плеера — это именно файл «Вставленный текст(1).txt», который ты сейчас загрузил, я могу взять его за основу и переписать конкретно под твой проект, не выдумывая недостающие функции.
Да напиши полную версию кода авр можно без unishift
Да. Я бы сделал AVR заново, без UniShift_Out_old.h/.c, но сохранил вашу схему выводов и логику сенсорной ленты.
По коду плеера подтверждается, что он ждёт именно пакет 0xFF + три ASCII-цифры: например, для трека 201 AVR должен передать FF 32 30 31. Плеер затем превращает эти три символа в номер 201 и запускает DFPlayer_Play_track_MP3(201).
Ниже полный вариант AVR. Я специально убрал зависимость от UniShift и вынес UART в отдельные функции, чтобы не писать напрямую в UDR из разных мест.
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include <stdint.h>
/* =========================================================
НАСТРОЙКИ
========================================================= */
#define N_TAKTS 14
/* Сенсоры */
#define SENS_OBUCH 9
#define SENS_KONTROL 10
#define SENS_YES 11
#define SENS_NO 12
#define SENS_PAUSE 13
/* Светодиоды */
#define LED_OBUCH 9
#define LED_KONTROL 10
#define LED_YES 11
#define LED_NO 12
#define LED_PAUSE 13
/* Количество вопросов */
#define NUM_QST 10
/* =========================================================
СДВИГОВЫЕ РЕГИСТРЫ / СЕНСОРНАЯ ЛЕНТА
Было в исходном UniShift:
DATA = PB5
CLK = PB7
SHCP = PD6
STCP = PD7
DATA INPUT = PD5
Светодиоды хранятся в Out[].
========================================================= */
#define OUT_DATA_PORT PORTB
#define OUT_DATA_DDR DDRB
#define OUT_DATA_PIN PB5
#define OUT_CLK_PORT PORTB
#define OUT_CLK_DDR DDRB
#define OUT_CLK_PIN PB7
#define SHIFT_CLK_PORT PORTD
#define SHIFT_CLK_DDR DDRD
#define SHIFT_CLK_PIN PD6
#define SHIFT_LATCH_PORT PORTD
#define SHIFT_LATCH_DDR DDRD
#define SHIFT_LATCH_PIN PD7
#define SENSOR_DATA_PORT PIND
#define SENSOR_DATA_DDR DDRD
#define SENSOR_DATA_PIN PD5
#define DS (1 << PD3)
/*
* В оригинальном UniShift было:
*
* N_takts_Out = 32
* Out[0..32]
*
* Оставляем этот же размер.
*/
#define N_TAKTS_OUT 32
/* =========================================================
ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
========================================================= */
volatile uint8_t num = 0;
volatile uint8_t num_old = 0;
volatile uint8_t play_active = 0;
/*
0 = ничего не воспроизводится
1 = воспроизводится
2 = плеер сообщил об окончании
*/
volatile uint8_t pause_state = 0;
/*
0 = воспроизведение
1 = пауза
*/
volatile uint8_t ik_new = 0;
volatile uint8_t punkt = 1;
volatile uint8_t razd = 1;
volatile uint8_t rndcase = 1;
volatile uint8_t was[NUM_QST + 1];
volatile uint8_t rnd_max = NUM_QST;
volatile uint8_t out = 0;
volatile uint8_t all = 0;
volatile uint8_t k = 0;
volatile uint8_t answ[NUM_QST + 1] =
{
0,
11,
12,
11,
12,
11,
12,
11,
12,
11,
11
};
/* Буфер светодиодов */
volatile uint8_t Out[N_TAKTS_OUT + 1];
/* Счётчик сдвига */
volatile uint8_t takt_cnt = 0;
/* =========================================================
UART
========================================================= */
static void UART_SendByte(uint8_t data)
{
while (!(UCSRA & (1 << UDRE)))
{
/* ждём освобождения регистра */
}
UDR = data;
}
/*
* Плеер использует ASCII_PROTOCOL.
*
* Для PLAY(201) отправляем:
*
* FF 32 30 31
*
* где:
* FF = начало пакета
* 32 = '2'
* 30 = '0'
* 31 = '1'
*/
static void PLAYER_SendTrack(uint16_t track)
{
uint8_t a;
uint8_t b;
uint8_t c;
if (track > 999)
track = 999;
a = track / 100;
b = (track / 10) % 10;
c = track % 10;
/*
* Сначала полностью дождаться предыдущего байта.
*/
UART_SendByte(0xFF);
UART_SendByte('0' + a);
UART_SendByte('0' + b);
UART_SendByte('0' + c);
}
/* Команды плееру */
static void PLAYER_Pause(void)
{
UART_SendByte('p');
}
static void PLAYER_Resume(void)
{
UART_SendByte('r');
}
static void PLAYER_Stop(void)
{
UART_SendByte('s');
}
/*
* Основная функция запуска трека.
*/
static void PLAY(uint16_t track)
{
/*
* Сбрасываем состояние паузы перед новым треком.
*/
pause_state = 0;
PLAYER_SendTrack(track);
/*
* После отправки пакета считаем, что трек запущен.
*/
play_active = 1;
}
/* =========================================================
UART RX
========================================================= */
/*
* Плеер при окончании трека присылает '@'.
*/
ISR(USARTRXC_vect)
{
uint8_t status;
uint8_t data;
status = UCSRA;
data = UDR;
(void)status;
if (data == '@')
{
play_active = 2;
}
}
/* =========================================================
СДВИГОВЫЙ РЕГИСТР
========================================================= */
/*
* Функция полностью заменяет Shift_Takt()
* из UniShift_Out_old.c.
*/
static void Shift_Takt(void)
{
uint8_t index;
/*
* Начало передачи.
*/
SHIFT_CLK_PORT &= ~(1 << SHIFT_CLK_PIN);
SHIFT_LATCH_PORT &= ~(1 << SHIFT_LATCH_PIN);
/*
* Выводим 32 бита.
*
* Сохраняем направление из оригинального UniShift.
*/
for (index = 0; index < N_TAKTS_OUT; index++)
{
/*
* DATA
*/
if (Out[index + 1])
OUT_DATA_PORT |= (1 << OUT_DATA_PIN);
else
OUT_DATA_PORT &= ~(1 << OUT_DATA_PIN);
/*
* CLK HIGH
*/
OUT_CLK_PORT |= (1 << OUT_CLK_PIN);
/*
* CLK LOW
*/
OUT_CLK_PORT &= ~(1 << OUT_CLK_PIN);
}
/*
* Защёлка.
*/
SHIFT_LATCH_PORT |= (1 << SHIFT_LATCH_PIN);
SHIFT_LATCH_PORT &= ~(1 << SHIFT_LATCH_PIN);
}
/* =========================================================
TIMER2
========================================================= */
ISR(TIMER2_OVF_vect)
{
TCNT2 = 2;
Shift_Takt();
}
/* =========================================================
ОПРОС СЕНСОРНОЙ ЛЕНТЫ
========================================================= */
static void Opros(void)
{
uint8_t j;
num = 0;
/*
* LATCH LOW
*/
SHIFT_LATCH_PORT &= ~(1 << SHIFT_LATCH_PIN);
_delay_us(1);
/*
* LATCH HIGH
*/
SHIFT_LATCH_PORT |= (1 << SHIFT_LATCH_PIN);
_delay_us(1);
/*
* Читаем 14 сенсоров.
*/
for (j = 1; j <= N_TAKTS; j++)
{
/*
* Сенсор активен низким уровнем.
*/
if (!(SENSOR_DATA_PORT & (1 << SENSOR_DATA_PIN)))
{
num = j;
}
/*
* Следующий сенсор.
*/
SHIFT_CLK_PORT &= ~(1 << SHIFT_CLK_PIN);
_delay_us(1);
SHIFT_CLK_PORT |= (1 << SHIFT_CLK_PIN);
_delay_us(1);
}
_delay_ms(1);
}
/* =========================================================
ВСЕ СВЕТОДИОДЫ ВЫКЛ
========================================================= */
static void ALL_OFF(void)
{
uint8_t j;
/*
* В исходном коде светодиоды 1..8
* выключались здесь.
*/
for (j = 1; j < LED_OBUCH; j++)
{
Out[16 + j] &= ~DS;
}
}
/* =========================================================
ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ LED
========================================================= */
static void LED_On(uint8_t led)
{
if (led <= N_TAKTS_OUT)
{
Out[16 + led] |= DS;
}
}
static void LED_Off(uint8_t led)
{
if (led <= N_TAKTS_OUT)
{
Out[16 + led] &= ~DS;
}
}
/* =========================================================
ПАУЗА / ПРОДОЛЖЕНИЕ
========================================================= */
static void HandlePause(void)
{
/*
* ВАЖНО:
* пауза разрешена только когда действительно
* есть активный трек.
*/
if (play_active != 1)
return;
if (num != SENS_PAUSE)
return;
/*
* Первый клик = PAUSE
* Второй клик = RESUME
*/
if (pause_state == 0)
{
PLAYER_Pause();
pause_state = 1;
LED_On(LED_PAUSE);
}
else
{
PLAYER_Resume();
pause_state = 0;
LED_Off(LED_PAUSE);
}
/*
* Ждём отпускания сенсора.
* Это важно: без этого одно физическое нажатие
* может быть обработано несколько раз.
*/
do
{
Opros();
}
while (num == SENS_PAUSE);
LED_Off(LED_PAUSE);
_delay_ms(50);
}
/* =========================================================
ОБРАБОТКА ОКОНЧАНИЯ ТРЕКА
========================================================= */
static void HandleTrackFinished(void)
{
if (play_active != 2)
return;
play_active = 0;
pause_state = 0;
if (num_old != 0)
{
LED_Off(num_old);
num_old = 0;
}
LED_Off(LED_PAUSE);
}
/* =========================================================
ВЫБОР ТРЕКА НА СЕНСОРЕ
========================================================= */
static void HandleTrackSensor(void)
{
/*
* Сенсоры 1..8 = выбор элементов.
*/
if (num == 0 || num >= SENS_OBUCH)
return;
/*
* Повторное нажатие того же сенсора:
* остановить трек.
*/
if (num_old == num)
{
PLAYER_Stop();
play_active = 0;
pause_state = 0;
LED_Off(num_old);
LED_Off(LED_PAUSE);
num_old = 0;
}
else
{
/*
* Запускаем выбранный трек.
*/
PLAY(num);
/*
* Выключаем старый LED.
*/
if (num_old != 0)
LED_Off(num_old);
/*
* Включаем новый LED.
*/
LED_On(num);
num_old = num;
}
/*
* Ждём отпускания сенсора.
*/
do
{
Opros();
}
while (num != 0);
_delay_ms(50);
}
/* =========================================================
РЕЖИМ ОБУЧЕНИЯ
========================================================= */
static void STUDY(void)
{
/*
* Включаем LED "Обучение".
*/
LED_On(LED_OBUCH);
/*
* Основной трек обучения.
*
* В вашем исходном коде это 201.
*/
PLAY(201);
/*
* Больше НЕ делаем:
*
* _delay_ms(1000);
*
* потому что во время этого delay AVR не опрашивает
* сенсоры.
*/
razd = 1;
punkt = 1;
/*
* Ждём перехода в "Контроль".
*/
while (num != SENS_KONTROL)
{
Opros();
/*
* Пауза.
*/
HandlePause();
/*
* Окончание трека.
*/
HandleTrackFinished();
/*
* Выбор сенсора 1..8.
*/
HandleTrackSensor();
/*
* Если нажали "Контроль",
* выходим.
*/
if (num == SENS_KONTROL)
break;
_delay_ms(5);
}
/*
* Остановка плеера.
*/
PLAYER_Stop();
play_active = 0;
pause_state = 0;
num_old = 0;
ALL_OFF();
LED_Off(LED_OBUCH);
LED_Off(LED_PAUSE);
/*
* Ждём отпускания "Контроль".
*/
do
{
Opros();
}
while (num == SENS_KONTROL);
_delay_ms(100);
}
/* =========================================================
СЛУЧАЙНЫЙ ВОПРОС
========================================================= */
static void RND(void)
{
uint8_t attempts;
attempts = 0;
do
{
rndcase = 1 + (rand() % rnd_max);
attempts++;
/*
* Защита от бесконечного цикла.
*/
if (attempts > 50)
{
uint8_t i;
for (i = 0; i <= NUM_QST; i++)
was[i] = 0;
attempts = 0;
}
}
while (was[rndcase] != 0);
was[rndcase] = 1;
/*
* Когда почти все вопросы использованы,
* очищаем историю.
*/
all = 0;
{
uint8_t i;
for (i = 1; i <= rnd_max; i++)
{
if (was[i])
all++;
}
if (all >= rnd_max - 2)
{
for (i = 0; i <= NUM_QST; i++)
was[i] = 0;
/*
* Текущий вопрос считаем использованным.
*/
was[rndcase] = 1;
}
}
}
/* =========================================================
РЕЖИМ КОНТРОЛЯ
========================================================= */
static void KONTROL(void)
{
LED_On(LED_KONTROL);
/*
* Вступительный трек контроля.
*/
PLAY(202);
/*
* В оригинале был delay 1000 мс.
* Оставляем небольшую задержку, но не блокируем
* управление на целую секунду.
*/
_delay_ms(100);
srand(TCNT2);
out = 1;
razd = 1;
punkt = 1;
while (num != SENS_OBUCH)
{
Opros();
/*
* Выбор нового вопроса.
*/
if (out == 1)
{
RND();
_delay_ms(50);
/*
* В исходном коде:
* PLAY(rndcase + 100)
*/
PLAY(rndcase + 100);
out = 0;
}
/*
* Пауза.
*/
HandlePause();
/*
* Окончание воспроизведения.
*/
if (play_active == 2)
{
play_active = 0;
pause_state = 0;
LED_Off(LED_PAUSE);
}
/*
* Ответ "ДА".
*/
if (num == SENS_YES)
{
if (num == answ[rndcase])
{
LED_On(LED_YES);
PLAY(203);
_delay_ms(100);
LED_Off(LED_YES);
out = 1;
}
else
{
LED_On(LED_NO);
PLAY(204);
_delay_ms(100);
LED_Off(LED_NO);
out = 1;
}
/*
* Ждём отпускания сенсора.
*/
do
{
Opros();
}
while (num == SENS_YES);
}
/*
* Ответ "НЕТ".
*/
if (num == SENS_NO)
{