У меня есть устройство на микроконтроллере ATMega16, которое должно постоянно отправлять результаты измерений АЦП через USART. Контроллер работает на частоте 16 МГц с внешним кристаллом, а предварительный делитель АЦП установлен на 128. Я пробовал два метода преобразования АЦП и отправки результатов.
int main(void) {
...
while (true) {}
}
ISR(ADC_vect) {
USARTSendByte(ADCL);
USARTSendByte(ADCH);
ADCSRA |= 1 << ADSC;
}
int main(void) {
...
// main loop
while (true) {
if (adcEnabled) {
ADMUX |= channel;
ADCSRA |= (1 << ADSC);
while (!(ADCSRA & (1 << ADIF))) {
// Do nothing
}
ADCSRA |= (1 << ADIF); // Clear ADIF
USARTSendByte(ADCL);
USARTSendByte(ADCH);
}
}
}
Я выполнил ряд тестов, которые состояли из отправки 32 блоков по 512 байт (всего 16384 байта) через USART и измерения времени передачи. В первом случае (прерывания) среднее время составило 1623.13ms
. Наименьший результат составил 1535 мс, а наибольший — 1712 мс. Во втором случае результат был на 1600.38ms
22,75 мс меньше, чем в предыдущем случае, с наименьшим результатом 1530 мс и наибольшим результатом 1679 мс.
Итак, вопрос: действительно ли прерывания снижают производительность АЦП и почему это происходит, или результаты моих тестов были неубедительными?
То, что вы, вероятно, измеряете, - это накладные расходы на вход и выход из ISR в программном обеспечении. Поскольку вы не декорировали ISR, он сохраняет все состояние регистра и восстанавливает его по возвращении. Ваш ISR может быть объявлен голым, и вы можете самостоятельно управлять сохранением SREG и других важных регистров (например, на встроенном ассемблере), и это приведет вас к тому, чего вы хотите.
Вы также не хотите вызывать функции из ISR. Просто переместите байты в глобальный буфер и сделайте что-нибудь с буферизованными данными из вашего основного цикла (например, отправьте их через UART).
Из руководства avr-gcc :
определить ISR_NAKED
ISR создается без кода пролога или эпилога. Пользовательский код отвечает за сохранение состояния машины, включая регистр SREG, а также за размещение reti() в конце процедуры обработки прерывания.
Планируете ли вы делать что-то еще в микропроцессоре, кроме отправки показаний АЦП? Если нет, забудьте об использовании прерываний и просто используйте все в основном цикле. Прерывания действительно полезны, только если вы пытаетесь сделать что-то еще в «фоновом режиме».
Не утруждайте себя (пока) использованием ассемблера. Вам это не нужно.
Однако важный (общий) момент: не вызывайте блокирующие функции из ISR! ISR никогда не должен ждать чего-либо .
Викатку прав: используйте какой-то «буфер», который может быть таким же простым, как одна глобальная uint16_t
переменная, в которую ISR записывает свой результат, а основной цикл извлекает его, когда может.
Еще одно замечание: в своем коде вы получаете результат ADC, отправляете его через USART, а затем начинаете следующее преобразование. Вы должны иметь возможность получить результат АЦП, начать следующее преобразование и только потом передавать данные, в то время как АЦП уже обрабатывает следующую выборку в это же время. Или вы можете посмотреть на «автономный» режим АЦП, где он автоматически запускает следующее преобразование, как только завершится предыдущее.
Чтобы проиллюстрировать, как может быть реализован общий подход к буферизации, это может служить примером:
volatile uint16_t adcValueBuffer;
volatile uint8_t adcValueBufferValidFlag;
ISR(ADC_vect) {
adcValueBuffer = ADC;
adcValueBufferValidFlag = 1; // This signals that the ADC provided a new value for the code outside the ISR.
ADCSRA |= 1 << ADSC;
}
int main() {
uint16_t adcValueCache; // Local variable which will hold the ADC value until it is completely transmitted.
while( true ) {
// Wait for the ISR to signal that a new value is available:
while ( adcValueBufferValidFlag == 0 ) {
}
adcValueBufferValidFlag = 0; // Re-set flag. Will be set again by the ISR when a new ADC value becomes available.
// Make sure that we read the buffered value atomically:
cli();
adcValueCache = adcValueBuffer;
sei();
USARTSendByte( (uint8_t)adcValueCache );
USARTSendByte( (uint8_t)(adcValueCache >> 8));
}
}
Если/когда АЦП постоянно производит выборку быстрее, чем USART может передавать данные, невозможно не потерять выборки. В этом случае вам нужно будет синхронизировать оба процесса (выборку и передачу), как вы это уже делали, начиная следующую выборку только после завершения (более медленной) операции передачи.
Кроме того, очереди или циклические буферы (также известные как кольцевые буферы ) часто используются для обмена данными с различной скоростью между асинхронными компонентами программы: ISR добавляет данные в буфер, а затем основной цикл берет (и удаляет) как можно больше элементов. как доступны в буфере, когда у него есть время.
Эти буферы могут быть реализованы в двух вариантах относительно условий переполнения: один тип отбрасывает любые новые данные, когда буфер заполнен, другой просто перезаписывает самые старые данные в буфере.
Если это то, что вам нужно, просто введите в поиск «avr round buffer»; существует множество реализаций.
main()
например:cli(); temp = adc_value; sei(); processAdcValue( temp );
while ((UCSRA & (1 << UDRE)) == 0) {/*Do nothing*/} UDR = temp;
. Поэтому, если прерывание происходит до отправки временных данных, я в конечном итоге отправлю другое значение из-за temp
перезаписи. Собственно поэтому я и хотел отключить прерывания при передаче данных.
мышеловка