Нужна помощь по коду для AVR (ATTiny85)
не нашел как повторно редактировать свое сообщение, прерывание
ISR(PCINT0_vect) {
// Отключаем прерываения по смене состояния на INT0
GIMSK &= ~_BV(PCIE);
PCMSK &= ~_BV(PCINT0);
flag = 1; // Взводим флаг
}
Спасибо что откликнулись, но чет не работает или я не понимаю как это должно работать… Мой первый код мигает светодиодом, чем светлее тем чаще он мигает, я это вижу невооруженным глазом просто поднося к нему фонарик. Ваш код просто светит (полагаю на самом деле очень быстро мигает), и я не пойму куда поставить задержку чтобы в железе это выглядело как первый вариант. И еще, в будущем я хочу чтобы контроллер спал пока ждет перехода уровня, а у вас он бодро крутит пустой цикл получается? То есть потом, когда все наладится, после if(flag){} можно будет уходить в сон с пробуждением по переполнению таймера или по смене состояния на входе?
Вот к каким изысканиям я пришел, но это все еще не работает как задумано. Мне надо чтобы частота мигания светодиода зависела от текущего уровня освещенности.
#define LED_A PB2
#define LED_K PB0
#define F_CPU 1000000L
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/io.h>
volatile uint8_t lightValue, lightCycle;
ISR(PCINT0_vect) {
GIMSK &= ~_BV(PCIE); // Отключаем прерываения по смене состояния
lightCycle = 1; // Взводим флаг
lightValue = TCNT1;
}
ISR(TIMER1_OVF_vect) {
lightValue = 255; // Устанавливаем максимальное значение
lightCycle = 1; // Устанавливаем флаг окончания измерения
}
uint8_t inline getLight(){
DDRB |= _BV(LED_A) | _BV(LED_K); // Порты на выход
PORTB |= _BV(LED_A); // и моргнуть
PORTB &= ~_BV(LED_K);
_delay_ms(10);
PORTB &= ~_BV(LED_A); // Подаем обратное напряжение
PORTB |= _BV(LED_K);
_delay_ms(10);
DDRB &= ~_BV(LED_K); // Катод на вход
PORTB &= ~(_BV(LED_K) | _BV(LED_A)); // Отклочаем подтяжку
PRR &= ~_BV(PRTIM1); // Включаем питание счетчика
TCNT1 = 0;
TCCR1 = 0;
TCCR1 |= _BV(CS13) | _BV(CS12) | _BV(CS11); // Запускаем счетчик на частоте clk/8192
TIMSK |= _BV(TOIE1); // Разрешаем прерывания по переполнению счетчика
GIMSK |= _BV(PCIE); // Включаем прерывание по смене состояния PB0
PCMSK |= _BV(PCINT0);
lightCycle = 0;
while(!lightCycle){
// ---------------------Тут будем спать
}
TIMSK &= ~_BV(TOIE1); // Запрещаем прерывания по переполнению счетчика
GIMSK &= ~_BV(PCIE); // Запрещаем прерывания по смене состояния пина
PRR |= _BV(PRTIM1); // Отключаем счетчик
return lightValue;
}
int main(void) {
sei();
while(1){
getLight(); // Получаем освещенность
while (lightValue--) { // Чем темнее, тем больше пауза
_delay_ms(1); // просто для наглядности
}
}
}
Только начал разбираться во всех этих дебрях, вероятно где-то сильно туплю 😵
на первый взгляд, похоже с частотой счетчика не попали. В первом коде крутится практически пустой цикл измерения, пусть это тактов 10-20, а в последнем коде устанавливаете частоту /512.
Во втором коде делитель /8192, переполнение счетчика наступает каждые чуть более 2 секунд, с такой частотой светодиод и моргает независимо от освещенности. Не срабатывает прерывание по смене состояния пина PCINT0, и я не пойму почему. На этом же железе первый код прекрасно отрабатывает появление на этом же пине “0”, что собственно и есть смена состояния, просто отслеживается не через прерывание а прямым чтением регистра.
Попробуйте, следующий код, в симуляторе atmel studio отрабатывает, но конечно pb0 я в ручную сбрасываю в 0, чтобы прерывание сработало. Если не решите вопрос - на выходных соберу прототип, пока со временем вилы.
volatile uint8_t flag = 1;
uint8_t light = 0;
ISR(PCINT0_vect) {
GIMSK &= ~_BV(PCIE); // Отключаем прерываения по смене состояния
PCMSK &= ~_BV(PCINT0);
flag = 1; // Взводим флаг
}
int main(void)
{
while (1)
{
if(flag){
// задержка для визуализации результата замера
if(light){
while(light--){
_delay_ms(1);
}
}
cli();
DDRB |= _BV(LED_A) | _BV(LED_K); // Порты на выход
PORTB |= _BV(LED_A); // Моргнуть
PORTB &= ~_BV(LED_K);
_delay_ms(10);
PORTB &= ~_BV(LED_A); // Подаем обратное напряжение
PORTB |= _BV(LED_K);
DDRB &= ~_BV(LED_K); // Катод на вход
PORTB &= ~(_BV(LED_K) | _BV(LED_K)); // Отклочаем подтяжку
GIMSK |= _BV(PCIE); // Разрешаем прерывание по смене состояния PB0
PCMSK |= _BV(PCINT0);
light = 0;
flag = 0;
sei();
}
if(light<255)
{
light++;
}
}
}
Да, так работает так же как мой первый пример, при увеличении освещенности моргает чаще, в темноте реже. А как сделать чтобы light считался встроенным счетчиком, как в моей втором варианте? Если усыпить проц до срабатывания прерывания, он же не будет гонять цикл и считать.
А как сделать чтобы light считался встроенным счетчиком, как в моей втором варианте? Если усыпить проц до срабатывания прерывания, он же не будет гонять цикл и считать.
- если как у Вас, то я бы исправил
ISR(PCINT0_vect) {
...
lightValue = TCNT1L; // Значение, до которого досчитал таймер low byte
//lightValue = TCNT1; // если сделать lightValue типом uint16_t, то можно так
}
- есть один момент: цикл main->while(1) должен крутиться всегда и работать с последним значением освещенности, расчитанным параллельно в таймерах, нельзя вот так останавливать работу до получения значения while(!lightCycle){}. Если конечно это не особые требования функционала.
- У ATTiny85 нет TCNT1L/TCNT1H, у нее оба счетчика 8-битные.
- Я представлял себе алгоритм работы так: всегда крутим main->while(1), в котором вызываем getLight(), вся работа со светодиодом будет там, и там же проц будет засыпать внутри цикла while(!lightCycle){}. то есть после подачи обратного напряжения и настройки прерываний проц уснет, появление “0” на входе или переполнение таймера его будит и взводит флаг, после обработки прерываний идет возврат в цикл где условие уже не выполняется и происходит возврат в основную программу и мы продолжаем что-то там делать… ну как-то так
ISR(PCINT0_vect){...lightCycle=1;...} // появление "0" после "1"
ISR(TIMER1_OVF_vect){...lightCycle=1;...} // переполнение счетчика
int getLight(){
... // настройка портов и прерываний
flag = 0;
while(!lightCycle){sleep} // тут спим
return;
}
int main(void){
...
getLight();
...
}
странный конечно алгоритм, если проц спит, то он уже точно while проверять не может. А если его все таки разбудило прерывание, то зачем проверять флаг lightCycle ?
странный конечно алгоритм, если проц спит, то он уже точно while проверять не может.
он засыпает внутри while, и когда проснется тоже будет внутри, сходит в обработчик прерывания которое его разбудило, там он поднимет флаг, вернется из обработчика обратно в while, теперь условие уже не выполняется и он перепрыгнув while вернется в основную программу.
в принципе можно while заменить на if, сути это не изменит, после настройки флаг сбрасывается и проц идет спать, проснуться он может только от прерывания так что после пробуждения проверять флаг уже не надо.
у меня нет вопросов ко сну, этот момент вообще сейчас заменен пустым циклом. из этого цикла можно выйти только по двум условиям - либо счетчик отсчитал 2 секунды, либо на пине PB0 случился переход уровня. так вот переход уровня не случается, и я не понимаю почему.
у Вас рабочий код, на железе пока не могу проверить, но в симуляторе
GIMSK &= ~_BV(PCIE); не достаточно
PCMSK &= ~_BV(PCINT0); обязательно, в противном случае постоянно проваливался в прерывание ISR(PCINT0_vect)
второй момент - while(!lightCycle){} - это не сон, а ожидание, проц будет продолжать жрать питание. Термин “сон” - это перевод камня в состояние, при котором у него часть функции будут отключены и режим потребления питания будет минимален. Про сон погуглите watchdog и как этот таймер можно использовать, я не пользовался им поэтому не могу ничего утверждать.
второй момент - while(!lightCycle){} - это не сон, а ожидание
да, я об этом знаю 😃 пустой цикл я поставил на время отладки, чтобы исключить лишние глюки пока не разберусь с PCINT0.
GIMSK &= ~_BV(PCIE); не достаточно
PCMSK &= ~_BV(PCINT0); обязательно, в противном случае постоянно проваливался в прерывание ISR(PCINT0_vect)
Заработало!!! Господи, спасибо тебе что есть такие люди! Мой второй код заработал, только разогнал таймер до /128, а то моргало очень быстро, мало успевал накрутить. Огромное вам спасибо, я чуть с ума не сошел))
Вот рабочий код:
#define LED_A PB2
#define LED_K PB0
#define F_CPU 1000000L
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/io.h>
volatile uint8_t lightValue, lightCycle;
ISR(PCINT0_vect) {
GIMSK &= ~_BV(PCIE); // Запрещаем прерываения по смене состояния
lightCycle = 1; // Взводим флаг
lightValue = TCNT1; // Забираем значение счетчика
}
ISR(TIMER1_OVF_vect) {
TIMSK &= ~_BV(TOIE1); // Запрещаем прерывания по переполнению счетчика
lightCycle = 1; // Устанавливаем флаг окончания измерения
lightValue = 255; // Устанавливаем максимальное значение
}
uint8_t inline getLight(){
DDRB |= _BV(LED_A) | _BV(LED_K); // Порты на выход
PORTB |= _BV(LED_A); // и моргнуть
PORTB &= ~_BV(LED_K);
_delay_ms(10);
PORTB &= ~_BV(LED_A); // Подаем обратное напряжение
PORTB |= _BV(LED_K);
DDRB &= ~_BV(LED_K); // Катод на вход
PORTB &= ~_BV(LED_K); // Отклочаем подтяжку
PRR &= ~_BV(PRTIM1); // Включаем питание счетчика
TCNT1 = 0;
TCCR1 = 0;
TCCR1 |= _BV(CS13); // Запускаем счетчик на частоте clk/128
TIMSK |= _BV(TOIE1); // Разрешаем прерывания по переполнению счетчика
GIMSK |= _BV(PCIE); // Разрешаем прерывания по смене состояния PCINT0
PCMSK |= _BV(PCINT0); // на пине PB0
lightCycle = 0;
while(!lightCycle){
// Тут будем спать
}
TIMSK &= ~_BV(TOIE1); // Запрещаем прерывания по переполнению счетчика
GIMSK &= ~_BV(PCIE); // Запрещаем прерывания по смене состояния пина
PCMSK &= ~_BV(PCINT0); // <<<<< Иначе не работает
PRR |= _BV(PRTIM1); // Отключаем счетчик
return lightValue;
}
int main(void) {
sei();
while(1){
getLight();
while (lightValue--) { // Чем темнее, тем дольше пауза
_delay_ms(1);
}
}
}
В общем-то не долго я радовался, опять застрял, на этот раз с ADC… В общем мне надо контролировать напряжение питания контроллера, и сигнализировать при падении ниже заданного уровня (скажем 3.3V). Самым простым видиться измерение внутреннего источника 1.1V относительно текущего напряжения Vcc. Получилась такая функция, которая отлично работает:
uint16_t vBat;
uint16_t getVbat() {
ADMUX = 1 << MUX3 | 1 << MUX2; // Измеряем внутренний ИОН 1.1V относительно Vcc
_delay_ms(10); // Ждем стабилизации напряжения
ADCSRA |= _BV(ADSC); // Запускаме измерение
while ((ADCSRA & (1 << ADSC)) == 1); // Ждем окончания
uint16_t result = ADCL; // Забираем младший байт
result |= ADCH << 8; // забираем старший байт
vBat = 1099366L / result; // Вычисляем Vcc в mV, 4.880V / 5.000V = 0.976
// (1.1V*1024*1000) * 0.976 = 1099366L
return vBat;
}
int main(void)
{
ADCSRA |= _BV(ADEN); // Включаем АЦП
ADCSRA |= _BV(ADPS1) | _BV(ADPS0); // Частота АЦП clk/8 = 125кГц
while (1) {
...
getVbat();
if(vBat < 3300) beep(2);
...
}
}
Кроме постоянного контроля напряжения питания, мне надо иногда опрашивать датчик - измерять напряжение на PB3. Получилась вторая функция, которая тоже отлично работает:
uint16_t sens;
uint16_t getSens() {
DDRB |= _BV(SENS_PW); // Питание сенсора на выход
PORTB |= _BV(SENS_PW); // Включаем
DDRB &= ~_BV(SENS); // Сигнал сенсора на вход
ADMUX = 1 << MUX0 | 1 << MUX1; // Выбираем вход ADC3 (PB3)
_delay_ms(10);
ADCSRA |= _BV(ADSC); // Запускаме измерение
while ((ADCSRA & (1 << ADSC)) == 1); // Ждем окончания измерения
PORTB &= ~_BV(SENS_PW); // Выключаем питание сенсора
uint16_t result = ADCL; // Забираем младший байт
result |= ADCH << 8; // забираем старший байт
sens = vBat / 1024.0 * result; // Получаем текущее напряжение в mV
return sens;
}
но если я пытаюсь использовать их совместно, ничего не получается, они начинают друг другу мешать, и такой код работает неправильно
int main(void)
{
ADCSRA |= _BV(ADEN); // Включаем АЦП
ADCSRA |= _BV(ADPS1) | _BV(ADPS0); // Частота АЦП clk/8 = 125кГц
while (1) {
...
getVbat();
if(vBat < 3300) beep(2);
...
getSens();
while(sens--) _delay_ms(1);
blink(1);
}
}
убираю вызов любой из функций (подставив константу в соответствующую переменную), и оставшаяся все делает правильно, вместе блин ни в какую… гуглил, но толком не нагуглил, предлагают использовать прерывания, в обработчике переключать каналы… нафига не понял, и зачем оно мне. Надо чтобы функции работали независимо друг от друга, просто вызвал и получил результат. Контроль напряжения будет идти постоянно по вачдогу, а опрос датчика только при необходимости. Наверняка забыл в какой-то регистр что-то записать, но весь моск сломал не вижу, олень оленем… 😵
Отбой, разобрались…
неправильно ждал, вместо
while ((ADCSRA & (1 << ADSC)) == 1);
надо
while (ADCSRA & (1 << ADSC));