Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Микроконтроллеры для начинающих
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
Страницы: 1, 2, 3, 4, 5
Gorby
Цитата(chipstar.ru @ Nov 28 2014, 13:06) *
Согласен. AVR вышел после PIC. Некоторые просчеты архетектуры ранних PIC там устранили. В PIC, начиная с PIC18 то-же никаких трудностей с регистрами нет, но в целом PIC более привлекательны и понятны.

Дружище, проснитесь! Уж десять лет прошло. Расклад поменялся.
И даже тогда ПИКи рассматривались отдельно как недо-МК (по причине кривости системы команд и регистров, ограниченности ресурсов, невозможности программирования на Си).
А АВРы сразу позиционировались как замена i51 (легендарный 8515). Ну и до кучи мелкий 1200-й "заткнуть ПИКи".
Ну и не забываем, что Атмел "взял" всех своей флешовой версией i51. А 89С55й с 64К флеша!?

А про 18-е ПИКи не надо. Может и хороши они, да поздновато появились.
jhm
Цитата(Gorby @ Nov 28 2014, 14:28) *
Дружище, проснитесь! Уж десять лет прошло. Расклад поменялся.
И даже тогда ПИКи рассматривались отдельно как недо-МК (по причине кривости системы команд и регистров, ограниченности ресурсов, невозможности программирования на Си).
А АВРы сразу позиционировались как замена i51 (легендарный 8515). Ну и до кучи мелкий 1200-й "заткнуть ПИКи".
Ну и не забываем, что Атмел "взял" всех своей флешовой версией i51. А 89С55й с 64К флеша!?
А про 18-е ПИКи не надо. Может и хороши они, да поздновато появились.

Учить 8-битники сейчас нет смысла. Их время уходит. Хотя я сам их часто ипользую ввиду простоты и удобства для небольших задач.
Нужно начинать сразу с Cortex M0, самого простого 32бит контроллера. Таким образом сразу привыкать к мейнстриму.
paskal
Очень интересует, поддерживает ли CodeVisionAVR язык C++?
Сергей Борщ
Цитата(paskal @ Aug 17 2015, 20:48) *
Очень интересует, поддерживает ли CodeVisionAVR язык C++?
Он и простой С не поддерживает. Он компилит со своего языка, похожего на C.
paskal
А присутствует ли у него стандартная библиотека Си?
Сергей Борщ
www.google.com->"codevisionavr manual"
Tonotuh
Подскажите где можно посмотреть соответствие настроек #pragma config? Проблемма такая Нужно настроить PIC18f97j94 на работус проэктом USB Keyboard (из примера микрочипа) от кварца 12МГц. Для этого надо поставить предделитель на 3, но в настройках прописано
CODE

#pragma config STVREN = ON // Stack overflow reset
#pragma config XINST = OFF // Extended instruction set
#pragma config BOREN = ON // BOR Enabled
#pragma config BORV = 0 // BOR Set to "2.0V" nominal setting
#pragma config CP0 = OFF // Code protect disabled
#pragma config FOSC = FRCPLL // Firmware should also enable active clock tuning for this setting
#pragma config SOSCSEL = LOW // SOSC circuit configured for crystal driver mode
#pragma config CLKOEN = OFF // Disable clock output on RA6
#pragma config IESO = OFF // Internal External (clock) Switchover
#pragma config PLLDIV = NODIV // 4 MHz input (from 8MHz FRC / 2) provided to PLL circuit
#pragma config POSCMD = NONE // Primary osc disabled, using FRC
#pragma config FSCM = CSECMD // Clock switching enabled, fail safe clock monitor disabled
#pragma config WPDIS = WPDIS // Program memory not write protected
#pragma config WPCFG = WPCFGDIS // Config word page of program memory not write protected
#pragma config IOL1WAY = OFF // IOLOCK can be set/cleared as needed with unlock sequence
#pragma config LS48MHZ = SYSX2 // Low Speed USB clock divider
#pragma config WDTCLK = LPRC // WDT always uses INTOSC/LPRC oscillator
#pragma config WDTEN = OFF // WDT disabled; SWDTEN can control WDT
#pragma config WINDIS = WDTSTD // Normal non-window mode WDT.
#pragma config VBTBOR = OFF

строчка #pragma config PLLDIV = NODIV не совсем понятна, где найти соответствие NODIV, где искать остальные возможные варианты? Пробовал поставить делитель цифрами, выдает ошибку ([1225] configuration value '3' not recognized for configuration setting 'PLLDIV'), поставил DIV3, ошибку не выдал, но то что это соответствует делителю 3 нет уверенности. Тоже самое и с настройкой FOSC = FRCPLL (для работы от 12МГц надо перенастроить)
Ruslan1
Цитата(Tonotuh @ Sep 21 2015, 13:53) *
Подскажите где можно посмотреть соответствие настроек #pragma config?

В хелпе к оболочке.
B MPLAB IDE 8.** это лежит (менюшки в оболочке): Help->Topics->PIC18 Config settings->PIC18F9xxx -> PIC18F97J94

В пристегнутом файле оно.
Нажмите для просмотра прикрепленного файла

Tonotuh
Спасибо. Сегодня нашел этот хелп, после переустановки новой версии пакета.
lyric
Здравствуйте.
Начинаю изучать программирование AVR, есть много глупых вопросов, и лучшей темы чтобы их задать я не нашёл. Так вот, сначала некоторое описание ситуации, потом вопросы:

Работать буду с Atmega644. Писать собираюсь на С, специально для этих целей начал его учить, а язык ассемблера при этом не знаю.
Есть в железе собранный прибор(назовём его прибор А), построенный на этих МК, надо сделать на него программу, - это есть цель.
Есть другой прибор, который производится и продаётся (назовём его прибор Б), построенный на этом же МК, и есть к нему программа, написанная в CodeVisionAVR.

Читаю даташит на Atmega644, дошёл до 24-й страницы, где есть примеры кода записи и чтения EEPROM. В общих чертах эти механизмы работы с EEPROM понятны. Там показаны операции с регистрами МК, как на языке ассемблера, так и на С.

Так вот, после этого смотрю в программу на прибор Б, на подпрограммы чтения и записи EEPROM, и не вижу там ни одного названия регистра. Чтение с EEPROM там строится присвоением значений EEPROM-переменных к обычным, - просто копируются в оперативную память, как я понимаю. Точно так же строится запись, только наоборот, присвоением значений изменённых переменных EEPROM-переменным, и на этом всё, никаких регистров не упоминается. И вообще в программе в принципе нет упоминаний регистров МК, везде только переменные.
В связи с чем я не понимаю за счёт чего осуществляются операции работы с EEPROM.
Соответственно, все остальные примеры кода из даташита будут вызывать такой же вопрос.

Вопрос №1: Чтение/запись памяти и другие операции, требующие непосредственной работы с регистрами МК осуществляются в каких-то подключаемых библиотеках?

В этой программе (на прибор Б) подключены следующие библиотеки:
<mega644.h>
<delay.h>
<stdlib.h>
<lcd.h>

На первую библиотеку из списка описания не нашёл (ткните носом, если где-то есть), а в других вышеназванных библиотеках функций работы с EEPROM не увидел.

Вопрос №2: Что за тип данных такой bit в CodeVisionAVR? В языке С ведь нет типа boolean...

Вопрос №3: Посоветуйте среду разработки. Сомневаюсь между древней CodeVisionAVR 1.25.8 и Atmel Studio 6.1.
SlavaV
Зачем придумывать велосипед при использовании AVR, есть же Atmel Studio (на данный момент 7) в ней всё уже имеется (ASF) работа с прерываниями, портами, задержками и т.д. хороший help
главное в вашем случае подкорректировать board.h.
sovas
Доброй ночи прошу не пинать только учусь не могу понять как считать логарифм помогите разобраться .
#include <util/delay.h>
#include "lcd_lib.h"
#include <math.h>
#include <stdio.h>

// объявляем глобальные переменные
char lcd_buffer[16];
float val;


int main()
{
LCDinit();
while(1)
{

val=log10(5);
sprintf(lcd_buffer, "otvet%i", val);
LCDclr();
LCDGotoXY(0,0);
LCDstring(lcd_buffer, 16);

_delay_ms(300);
}
}
Dog Pawlowa
Цитата(sovas @ Jul 24 2016, 00:14) *
помогите разобраться .

И? пример работает?
Что не работает?
esaulenka
Цитата(Dog Pawlowa @ Jul 27 2016, 21:57) *
И? пример работает?
Что не работает?

Как минимум, printf("%i") ждёт int, а не float.
Но тогда бы мы услышали "работает, но какую-то ерунду выводит".
sovas
sprintf(lcd_buffer, "otvet%f", val); Даже если так пишу выводит знак вопроса и все , помогите разобраться как вывести корректно .
esaulenka
Больше самостоятельности!

LCDstring("1234567890123456", 16); - работает?
sprintf (lcd_buf, "1234567890123456"); LCDstring(lcd_buf, 16); - работает? (кстати, почитайте про sprintf - буфер нужен чуть больше: в конец ещё нолик пишется)
int a = 10; sprintf (lcd_buf, "%d", a); LCDstring(lcd_buf, 16); - работает?
float b = 12.34; sprintf (lcd_buf, "%f", cool.gif; LCDstring(lcd_buf, 16); - работает?

И только потом смотреть на логарифмы...

Ну и код этого самого LCDstring() вместе со всем, что оно вызывает, надо смотреть.
DASM
мне ваще вот эт не нра val=log10(5);
напишите val=log10(5.); с точкой или printf ("%f\r\n", (float)5); (что уже некрасиво делать)
skripach
Цитата(sovas @ Jul 30 2016, 00:40) *
sprintf(lcd_buffer, "otvet%f", val); Даже если так пишу выводит знак вопроса и все , помогите разобраться как вывести корректно .

Нужно убедится (включить в настройках компилятора) что sprintf умеет глотать float.
Ну и модификатор, что-нибудь вроде: sprintf(lcd_buffer, "otvet%9.3f", val);
lyric
Здравствуйте.
Я только начинаю вникать в AVR.

Есть атмега644, стоит в простенькой самодельной отладочной плате,. Сейчас к атмеге этой подключен только один светодиод, заставляем его моргать.
В качестве программатора-отладчика используется JTAG ICE MK2, в качестве среды разработки - Atmel Studio 6.1.

Сделана простейшая программа, которая на секунду включает светодиод, потом секунду держит его отключенным и так далее.

Проблема в том, что временной интервал в 1 секунду выдерживается только когда в проекте частоту F_CPU указываю 1000000. Если указываю 8000000, - то 1 секунда превращается в 8 секунд. Когда ставлю частоту 16000000 - то получается 16 секунд интервал. Для задания временного интервала используется библиотека util/delay.h.

Но ведь эта библиотека должна обеспечивать всегда одинаковую, указанную ей задержку, а не увеличивать её пропорционально частоте МК, верно? В чём тут проблема может быть?

Я залазил в этот h-файл библиотеки delay, ставил там свою частоту нужную, вместо 1000000 - но это ничего не изменило.

_____________________________________________

Вторая проблема - программатор-отладчик мой при внутрисхемной отладке по шагам доходя до строки с упоминанием delay - из текста программы переходит в библиотеку и всё, дальше отладку можно только прервать, обратно из библиотеки он уже не выходит никак. Подскажиет что я делаю не так?

код программы:

#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>


void preset()
{
DDRD = 0b11111111; // 0xFF
}


int main(void)
{
preset();
while(1)
{PORTD = 0b11111111;
_delay_ms(1000);
PORTD = 0b00000000;
_delay_ms(1000);
}

}
sigmaN
Частота задается программированием фьюзов
В #define F_CPU 16000000 ваша задача поместить РЕАЛЬНОЕ значение частьты. Сейчас ваша мега работает на частоте 1МГц и поэтому задержка сходится только когда вы не врете компилятору и делаете F_CPU 1000000

Цитата
Я залазил в этот h-файл библиотеки delay,
Никогда не лазьте в библиотеки и ничего там не правьте!

Программировать фьюзы тут
Нажмите для просмотра прикрепленного файла

При неправильной установке тактирования контроллер перестанет выходить на связь с программтором. Будьте осторожны!(восстановить потом можно, но это потребует дополнительного шаманства с аппаратной частью, что для новичка может быть сложно)
Lagman
Цитата(lyric @ Mar 10 2017, 12:36) *
Проблема в том, что временной интервал в 1 секунду выдерживается только когда в проекте частоту F_CPU указываю 1000000.

может и не правильно но ...
http://radiokot.ru/forum/viewtopic.php?p=1...4f75ff#p1540750
sigmaN
Предложенный способ задефайнить глобально F_CPU с помощью командной строки компилятора конечно-же правильный.
Однако вариант
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>

тоже вполне рабочий т.к. #define F_CPU расположен ПЕРЕД include.
lyric
Цитата(sigmaN @ Mar 10 2017, 17:22) *
Частота задается программированием фьюзов
В #define F_CPU 16000000 ваша задача поместить РЕАЛЬНОЕ значение частьты. Сейчас ваша мега работает на частоте 1МГц и поэтому задержка сходится только когда вы не врете компилятору и делаете F_CPU 1000000


Спасибо! Всё кратко, чётко и ясно.
А то сколько смотрел видео по AVR, сколько статей находил - нигде эту, казалось бы, простую инфу, не встречал: все сразу учат как мигать светодиодом, будто это основное, что нужно уметь в работе с МК)


Цитата(sigmaN @ Mar 10 2017, 17:22) *
Программировать фьюзы тут
Нажмите для просмотра прикрепленного файла

При неправильной установке тактирования контроллер перестанет выходить на связь с программтором. Будьте осторожны!(восстановить потом можно, но это потребует дополнительного шаманства с аппаратной частью, что для новичка может быть сложно)


Да, я находил это окно. На Вашем скриншоте всё подробно описано, а в моей студии 6.1 в строке SUT_CKSEL сейчас написано "INTRCOSC_6CK_65MS".
Я так понимаю что чтобы использовать собственный RC, нужно чтобы это волшебное слово начиналось с букв "INT", - таких строк, начинающихся с "INT", всего в списке 6, и частота указана только в трёх из них, и везде одинаковая, - 128 килогерц. Как при этом выставить свою частоту - непонятно. Разве что без Atmel Studio 6.1, используя сторонний программатор для этого.

Цитата(Lagman @ Mar 10 2017, 17:25) *
может и не правильно но ...
http://radiokot.ru/forum/viewtopic.php?p=1...4f75ff#p1540750


Да, я был в этой теме, делал всё, как там, но результата не было, т.к. не в курсе был что нужно корректировать фьюзы для смены частоты.
sigmaN
Не понимаю почему вы сидите на студии 6.1, когда есть студия 7 из которой я и сделал скриншот.

Фьюзы и их значения в конце концов описаны в даташите и вы можете ввести данные прям в окошечки LOW и HIGH которые вы видете внизу этого окошка.. Короче разберетесь
EugeNNe
Куда в 7 Студии запрятали установку частоты МК?
lyric
Привет всем. Наверняка уже много раз этот вопрос поднимался, но не знаю как и где искать.

Программирую атмегу по интерфейсу JTAG. В программе я могу эти 4 ноги, занятые JTAGом, использовать как обычные входы/выходы?
Smoky
Цитата(lyric @ Sep 22 2017, 12:03) *
Привет всем. Наверняка уже много раз этот вопрос поднимался, но не знаю как и где искать.

Программирую атмегу по интерфейсу JTAG. В программе я могу эти 4 ноги, занятые JTAGом, использовать как обычные входы/выходы?


При отладке программ нельзя, а в рабочем режиме имеется возможность. В регистрах MCUCSR или MCUCR имеется хитрый бит JTD, переключением которого можно эти порты использовать в рабочем режиме. Правда его переключение довольно своеобразное, как пишут в руководстве необходимо произвести запись бита дважды за четыре такта...
lyric
Цитата(Smoky @ Sep 22 2017, 13:56) *
При отладке программ нельзя, а в рабочем режиме имеется возможность. В регистрах MCUCSR или MCUCR имеется хитрый бит JTD, переключением которого можно эти порты использовать в рабочем режиме. Правда его переключение довольно своеобразное, как пишут в руководстве необходимо произвести запись бита дважды за четыре такта...


Спасибо, попробую beer.gif
lyric
Здравствуйте, снова есть смешной (для сколь-нибудь опытных разработчиков) вопрос:

Снова про AVR, работаю в атмел студии 7.
Ниже привожу проект, который должен включать Порт B на 10 секунд, потом вЫключать на 10 секунд, снова включать и так далее.
Использую для этого таймер1 (16 бит).

Задача - понять как правильно использовать аппаратный таймер для создания, допустим, 30 своих независимых друг от друга программных таймеров с произвольными моментами включения и сброса для каждого из них в зависимости от состояния, допустим, какой-либо переменной в программе. Пока экспериментирую с одной переменной. МК работает на частоте 8МГц, от внутреннего источника, - как был, я его не калибровал и не знаю как это делать и надо ли вообще.. Делитель 256.

Код
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

unsigned int n_count=0;

void preset()
{
    DDRB = 0b11111111; // 0xFF
    PORTB = 0b11111111; // 0x00
    
        DDRD = 0b00001111; // 0xFF
        PORTD = 0b00000000; // 0x00
    
    DDRA = 0b00000000;
    PORTA = 0b11111111; // 0x00
}

void timer_ini(void)    //функция инициализации таймера
{
    TCCR1B |= (1<<WGM12); //установка режима работы CTC (сброс по совпадению)
    TIMSK1 |= (1<<OCIE1A); //устанавливаем бит разрешения прерывания первого счётчика по совпадению с OCIR1A (H и L)
    OCR1AH = 0b00001100; //записываем в регистр OCR1A число 3125 (при работе на частоте 8 МГц это будет давать прерывание каждые 0,1 сек)
    OCR1AL = 0b00110101; //
    TCCR1B |= (1<<CS12); //установка делителя 256
    
}

ISR(TIMER1_COMPA_vect) //Прерывание по достижению таймером значения регистра OCR1A (H и L)
{
n_count++;
if (n_count>=200)    {n_count=0;}
}

int main(void)
{
    preset();
    timer_ini();    
    sei(); //глобальное разрешение прерываний
    
    while(1)
    {
        if ((n_count>0)&&(n_count<100)) {PORTB = 0b11111111;}
else
    if ((n_count>100)&&(n_count<=200)) {PORTB = 0b00000000;};        
    }
}


И вот мой Порт В включается и выключается, - ДА, примерно каждые 10 секунд. Как измерил? - положил рядом с включаемым светодиодом свой телефон с запущенным секундомером. И Вот такой подсчёт времени в микроконтроллере даёт погрешность примерно 1 секунду в минуту. А за 4 минуты - МК уже врёт на 4 секунды - убегает вперёд относительно времени, которое на смартфоне...

Тогда я переставил код из бесконечного цикла в тело прерывания. Так МК убегает вперёд на 12 секунд за 10 минут... Это слишком большая погрешность.. Как быть? Это нормально и вызвана непостоянностью рабочей частоты? Погрешность эту можно как-то сократить? А если я 30 таких таймеров запилю - мне их все в этом же прерывании обрабатывать будет нормально или они, возможно, не будут успевать обрабатываться за прерывание?

Буду рад, примеру простого но более или менее точного программного таймера.

И ещё вопрос из фьюзов кроме CKDIV8 ещё какие-то влияют на рабочую частоту МК, если он работает от внутреннего генератора? То есть в моём случае есть всего 2 варианта частоты, - с включенным CKDIV8 это 1МГц, а с выключенным CKDIV8 это 8МГц и третьего не дано? Атмега644, если это важно.
Smoky
Я бы не стал слишком сильно доверять телефонному секундомеру... Но если вы хотите опираться на него как на эталон, попробуйте поварьировать данные в регистре OCR1A. Одна единица этого регистра изменит ваши 10 сек на 3,2 мсек.
x736C
Возможно, у вас лишняя единица где-то еще.

Значение для таймера считается без единицы. В вашем случае:
timer_value = ([10] мс * 8000 [кГц] / 256) - 1 = 3124.
Эта ошибка уже добавляет лишние 320 мкс на одну секунду или 192 мс на 10 минут.

Вероятно, на следующем уровне иерархии, когда обрабатываете свои счетчики, допускаете ту же ошибку.

Цитата(lyric @ Oct 6 2017, 14:22) *
Как быть? Это нормально и вызвана непостоянностью рабочей частоты? Погрешность эту можно как-то сократить?

Такой погрешности быть не должно. То есть на глаз, если положить рядом секундомер, интервалы должны быть неотличимы.
Но это в том случае, если используете внешний кварцевый резонатор или генератор. Если внутренний RC-генератор, то все может быть иначе. Но проверить можно в AtmelStudio в режиме симуляции. Там есть возможность замерить интервалы работы участков кода. Только необходимо упростить пример, уменьшив значение, загружаемое в счетчик. Накопление погрешностей можно отследить. Все остальное будет из-за несовершенства RC-генератора. Его можно откалибровать, насколько помню. См. документацию.

Цитата(lyric @ Oct 6 2017, 14:22) *
А если я 30 таких таймеров запилю - мне их все в этом же прерывании обрабатывать будет нормально или они, возможно, не будут успевать обрабатываться за прерывание?

Это надо проверять, кто ж вам ответит. Но перегрузить прерывание, происходящее раз в 100 мс — это надо очень постараться.


Цитата(lyric @ Oct 6 2017, 14:22) *
И ещё вопрос из фьюзов кроме CKDIV8 ещё какие-то влияют на рабочую частоту МК, если он работает от внутреннего генератора? То есть в моём случае есть всего 2 варианта частоты, - с включенным CKDIV8 это 1МГц, а с выключенным CKDIV8 это 8МГц и третьего не дано? Атмега644, если это важно.

Далее идет делитель CLKPR, который может дальше поделить эту частоту.
lyric
Smoky, x736C

Большое спасибо, Ваши советы и замечания помогли. Ну и кроме того я сделал прерывание каждые 5 миллисекунд (вместо каждых 100), и инкрементируемую переменную указал volatile вместо unsigned. теперь секундомер смартфона один в один бьёт с тем, что отсчитывает МК, - на глаз никак не отличить. По моим расчётам погрешность за каждую секунду теперь составляет 2 микросекунды, а за 10 минут, соответственно, - 1,2 миллисекунды. Итого удалось уменьшить погрешность примерно в 10000 раз, неплохо rolleyes.gif
lyric
Здравствуйте.
Снова есть вопросы по AVR.

В программе сделал динамическую семисегментную индикацию через SPI, индикация вызывается в прерывании таймера каждые 2 миллисекунды.
И написал так же функцию, которая опрашивает все 8 каналов АЦП (без прерывания по окончанию преобразования). Эта функция вызывается в основном цикле, опрашивает 1 канал за такт. Для первых двух каналов АЦП в этой функции производятся довольно тяжёлые математические вычисления (оверсемплинг, фильтрация, аппроксимация по 2 точкам, коррекция) в переменных типа FLOAT. Ну и на время этих вычислений я запрещаю прерывания, соответственно индикаторы мерцают... А хотелось бы чтобы они горели с постоянной яркостью.

1а) Если НЕ запрещать прерывания, то прерывания могут испортить переменные в ОЗУ, которые в обработчике этого прерывания никак не участвуют?

1б) Если могут испортить - то, получается, мне в каждом куске основного цикла где есть работа с типами int16_t, int32_t, float - всегда запрещать прерывания?

2) В Atmel Studio 7, если использовать стандартные функции работы с EEPROM, то функция записи/обновления переменной в EEPROM сама отключает прерывания на время своей работы? Или это надо вручную перед вызовом этой функции запретить прерывания, а после выполнения функции - снова разрешать? В обработчике прерывания эти переменные так же не используются. Сейчас без запрета прерываний всё работает вроде, но боюсь что мне просто везёт, а хотелось бы знать наверняка. Можно ли где-то посмотреть сам текст этих стандартных функций работы с EEPROM? В хедер-файле EEPROM.h этого кода нет.
Plain
Во-первых, прерывания сами по себе ничего не портят, это просто условное ветвление программы, а во-вторых, в прерываниях надо делать только то, что в них действительно нуждается.

Узел EEPROM — это отдельная схема на кристалле микроконтроллера, она тактируется собственным тактовым генератором, поэтому всё делает сама и не зависит ни от чего. Также, после её озадачивания записью и до завершения всех процессов она включает помпу, повышающую напряжение питания, потому что для работы с ячейками памяти такого типа требуется относительно высокое напряжение, т.е. начинает потреблять указанный в паспорте существенный и дополнительный к общему ток на указанный в паспорте же интервал.
lyric
Plain

это значит что в моём случае запрещать прерывания не нужно ни при расчёте АЦП ни при записи EEPROM?

В обработчике прерывания таймера есть только то что касается индикации, и инкремент трёх переменных для создания программных таймеров в основном цикле.
Plain
Цитата(lyric @ Aug 23 2018, 08:56) *
запрещать прерывания не нужно

Естественно.
Сергей Борщ
QUOTE (lyric @ Aug 23 2018, 08:56) *
ни при записи EEPROM?
Прерывание может не только менять данные, но и нарушать временнЫе интервалы между командами. При записи в ЭСППЗУ нужно сделать две записи в регистр EECR, между которыми не должно быть больше четырех тактов процессора. Естественно, если после первой записи произойдет прерывание - ни о каких четырех тактах речь идти уже не будет. Поэтому вот именно перед первой записью в EECR прерывания должны быть запрещены, а после второй их уже можно разрешать.
lyric
Цитата(Сергей Борщ @ Aug 23 2018, 14:55) *
Прерывание может не только менять данные, но и нарушать временнЫе интервалы между командами. При записи в ЭСППЗУ нужно сделать две записи в регистр EECR, между которыми не должно быть больше четырех тактов процессора. Естественно, если после первой записи произойдет прерывание - ни о каких четырех тактах речь идти уже не будет. Поэтому вот именно перед первой записью в EECR прерывания должны быть запрещены, а после второй их уже можно разрешать.


А если я использую стандартные функции работы с EEPROM, которые содержаться в компиляторе Атмел Студии, - всё равно вручную запрещать прерывания? Функция eeprom_update_word() сама этого не делает? Где-то можно посмотреть её код?
Сергей Борщ
QUOTE (lyric @ Aug 23 2018, 11:44) *
А если я использую стандартные функции работы с EEPROM, которые содержаться в компиляторе Атмел Студии, - всё равно вручную запрещать прерывания?
Если она не запрещает прерывания - надо делать это вручную.
QUOTE (lyric @ Aug 23 2018, 11:44) *
Функция eeprom_update_word() сама этого не делает? Где-то можно посмотреть её код?
Самое простое - в листинге дизассемблера.
lyric
Цитата(Сергей Борщ @ Aug 23 2018, 16:16) *
Если она не запрещает прерывания - надо делать это вручную.
Самое простое - в листинге дизассемблера.


Не запрещает. Придётся писать свою функцию записи/обновления EEPROM, видимо.
Спасибо за помощь, с EEPROM понятно a14.gif .

Подскажите пожалуйста ещё на эти вопросы:

Цитата(lyric @ Aug 23 2018, 10:06) *
1а) Если НЕ запрещать прерывания, то прерывания могут испортить переменные в ОЗУ, которые в обработчике этого прерывания никак не участвуют?

1б) Если могут испортить - то, получается, мне в каждом куске основного цикла где есть работа с типами int16_t, int32_t, float - всегда запрещать прерывания?


Какие ответы? rolleyes.gif
Plain
Цитата(lyric @ Aug 23 2018, 11:44) *
Функция eeprom_update_word() сама этого не делает?

Если не делает, то её фактически нет, разве что какому-то программисту от микроконтроллера понадобится только его EEPROM и ничего более.

На время подачи сигнала разблокирования записи в EEPROM прерывания требуется запретить, чтобы выполнить требования по разблокирующей последовательности (у микроконтроллеров PIC, например, это последовательная запись 55 и AA в регистр разблокировки), но далее, на саму запущенную процедуру записи, любая работа программы никакого влияния не оказывает, в т.ч. и её работа в прерываниях.
lyric
Снова здравствуйте.

AVR.

Есть 2 самописных функции.
Эти функции используют разные глобальные переменные, вообще никак не пересекаются между собой. Вызываются в оновном цикле, никак не завязаны на прерывания. В одной из них работа с SPI, в другой с АЦП.
Но при этом функция с АЦП влияет на работу функции с SPI. Нашёл 2 строчки из-за которых это происходит, но там просто математические вычисления, никакого отношения не имеющие к другой функции.

При включении любого уровня оптимизации это проявляется. На уровне -О0 всё работает нормально.

Подскажите в чём может быть дело?
DASM
код нужен
lyric
Цитата(DASM @ Sep 12 2018, 13:12) *
код нужен


CODE

#define F_CPU 8000000
#include <avr/io.h>
#include <stdlib.h>
#include <math.h>
#include <avr/interrupt.h>



//--------------------------------------------------------------------------------------------------------------глобальные переменные-----------------------------------------------------------------
volatile uint8_t R1=0, R2=0, R3=0, R4=0, R5=0, R6=0, R7=0, R8=0; //Переменные значений разрядов индикатора
volatile int16_t ADC_AI_1, ADC_AI_2; //Текущие значения АЦП без фильтрации
volatile float ADC_AI_1_ff, ADC_AI_2_ff, AI_1, AI_2, AI_1_fv, AI_2_fv;//Текущие значения АЦП после фильтрации, аппроксимированных значений аналоговых входов до коррекции и после
volatile int32_t accu1=0, accu2=0; //переменные для оверсемплинга АЦП
volatile uint8_t accu_count1=0, accu_count2=0; //переменные для оверсемплинга АЦП
volatile int16_t koeff_AI1=1;
volatile int16_t koeff_AI2=1;
volatile uint8_t DI_portD_Mask;
volatile uint8_t DI_portA_Mask_no_opros;
volatile uint8_t DI_portA_Mask;

uint8_t tip_AI1=1;
uint16_t calibr_ADC_min_AI1; // Калибровочное минимальное значение АЦП аналогового входа AI1
uint16_t calibr_ADC_max_AI1; // Калибровочное максимальное значение АЦП аналогового входа AI1
int16_t NPI_AI1; // Нижний предел измерения параметра с аналогового входа AI1
int16_t VPI_AI1; // Верхний предел измерения параметра с аналогового входа AI1
uint16_t koef_A_AI1; // Коэффициент коррекции А для параметра с аналогового входа AI1
int16_t koef_B_AI1; // Коэффициент коррекции B для параметра с аналогового входа AI1
uint8_t koef_filtra_AI1; // Коэффициент фильтрации параметра с AI1

uint8_t tip_AI2=2;
uint16_t calibr_ADC_min_AI2; // Калибровочное минимальное значение АЦП аналогового входа AI2
uint16_t calibr_ADC_max_AI2; // Калибровочное максимальное значение АЦП аналогового входа AI2
int16_t NPI_AI2; // Нижний предел измерения параметра с аналогового входа AI2
int16_t VPI_AI2; // Верхний предел измерения параметра с аналогового входа AI2
uint16_t koef_A_AI2; // Коэффициент коррекции А для параметра с аналогового входа AI2
int16_t koef_B_AI2; // Коэффициент коррекции B для параметра с аналогового входа AI2
uint8_t koef_filtra_AI2; // Коэффициент фильтрации параметра с AI2

//------------------------------------------------------------------------------------------------

void preset()//функция установки портов
{


//инициализация порта В
DDRB = 0b10110000; //конфигурация: 0 - вход. 1 - выход
PORTB = 0b01001111; //1 - включение подтягивающих резисторов для входов. 0 - задание выходам порта начальных значений ("отключено" - высокий уровень, "включено" - низкий уровень).
//инициализация порта C
DDRC = 0b11111111; //конфигурация: 0 - вход. 1 - выход
PORTC = 0b01111111; //1 - включение подтягивающих резисторов для входов. 0 - задание выходам порта начальных значений ("отключено" - высокий уровень, "включено" - низкий уровень).
//инициализация порта D
//конфигурация: 0 - вход:
DDRD &=~(1<<PD7); //Настраиваем ножку PD7 в режим входа
DDRD &=~(1<<PD6); //Настраиваем ножку PD6 в режим входа
DDRD &=~(1<<PD5); //Настраиваем ножку PD5 в режим входа
DDRD &=~(1<<PD4); //Настраиваем ножку PD4 в режим входа
DDRD &=~(1<<PD3); //Настраиваем ножку PD3 в режим входа
DDRD |=(1<<PD2); //Настраиваем ножку PD3 в режим вЫхода
//1 - включение подтягивающих резисторов для входов порта D:
PORTD |= (1<<PD7);
PORTD |= (1<<PD6);
PORTD |= (1<<PD5);
PORTD |= (1<<PD4);
PORTD |= (1<<PD3);
//1 - задание выходу №2 порта D начального значения "отключено" - высокий уровень:
PORTD |= (1<<PD2);
}

void symboll(uint8_t symm) //функция отображения символов на индикаторах
{
switch(symm)
{
case 1: SPDR = 0b10111011; break; //цифра 1
case 2: SPDR = 0b10001100; break; //цифра 2
case 3: SPDR = 0b10101000; break; //цифра 3
case 4: SPDR = 0b00111001; break; //цифра 4
case 5: SPDR = 0b01101000; break; //цифра 5
case 6: SPDR = 0b01001000; break; //цифра 6
case 7: SPDR = 0b10111010; break; //цифра 7
case 8: SPDR = 0b00001000; break; //цифра 8
case 9: SPDR = 0b00101000; break; //цифра 9
case 0: SPDR = 0b00001010; break; //цифра 0

case 11: SPDR = 0b10110011; break; //цифра 1 с точкой
case 12: SPDR = 0b10000100; break; //цифра 2 с точкой
case 13: SPDR = 0b10100000; break; //цифра 3 с точкой
case 14: SPDR = 0b00110001; break; //цифра 4 с точкой
case 15: SPDR = 0b01100000; break; //цифра 5 с точкой
case 16: SPDR = 0b01000000; break; //цифра 6 с точкой
case 17: SPDR = 0b10110010; break; //цифра 7 с точкой
case 18: SPDR = 0b00000000; break; //цифра 8 с точкой
case 19: SPDR = 0b00100000; break; //цифра 9 с точкой
case 10: SPDR = 0b00000010; break; //цифра 0 с точкой

case 20: SPDR = 0b00011000; break; //буква А
case 21: SPDR = 0b01001001; break; //буква B
case 22: SPDR = 0b01001110; break; //буква C
case 23: SPDR = 0b10001001; break; //буква D
case 24: SPDR = 0b01001100; break; //буква E
case 25: SPDR = 0b01001010; break; //буква G
case 26: SPDR = 0b11001110; break; //буква I
case 27: SPDR = 0b00010001; break; //буква K
case 28: SPDR = 0b01001111; break; //буква L
case 29: SPDR = 0b11011001; break; //буква N
case 30: SPDR = 0b11001001; break; //буква O
case 31: SPDR = 0b11000001; break; //буква O с точкой
case 32: SPDR = 0b00011100; break; //буква P
case 33: SPDR = 0b11101111; break; //нижнее подчёркивание
case 34: SPDR = 0b11111101; break; //минус
case 35: SPDR = 0b11111111; break; //ничего
case 36: SPDR = 0b10111110; break; //стрелка вверх
case 37: SPDR = 0b11001111; break; //стрелка вниз
case 38: SPDR = 0b11011101; break; //буква R
case 39: SPDR = 0b11010101; break; //буква R с точкой
case 40: SPDR = 0b01101000; break; //буква S
case 41: SPDR = 0b01100000; break; //буква S с точкой
case 42: SPDR = 0b01001101; break; //буква T
case 43: SPDR = 0b11001011; break; //буква U
case 44: SPDR = 0b00001011; break; //буква V
case 45: SPDR = 0b11110001; break; //буква Z
default: SPDR = 0b11111111; //ничего
}
}

void SPI_init()
{
SPCR = ((1<<SPE)|(1<<MSTR));
}

void ADC_init()
{
ADCSRA |= ((1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)); //Разрешение использования АЦП и делитель 64 (частота опроса 125кГц)
ADMUX=0;
}

void ADC_convert(void) //функция чтения каналов АЦП. Первые 2 канала - это аналогоые входы. Остальные 6 каналов - используются как дискретные входы.
{
uint16_t kod_acp=0;
uint8_t lock_0=0; //чтобы за один вызов функции выполнялось только одно преобразование АЦП, а не 0 и 7 в один раз
static float prom_out1=0, prom_out2=0;
unsigned char savee = SREG;


if (ADMUX==7)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00100000;} else {DI_portA_Mask_no_opros &= 0b11011111;}
lock_0=1;
SREG= savee;
ADMUX=0;
}

if (ADMUX==6)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00010000;} else {DI_portA_Mask_no_opros &= 0b11101111;}
SREG= savee;
ADMUX=7;
}

if (ADMUX==5)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00001000;} else {DI_portA_Mask_no_opros &= 0b11110111;}
SREG= savee;
ADMUX=6;
}

if (ADMUX==4)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00000100;} else {DI_portA_Mask_no_opros &= 0b11111011;}
SREG= savee;
ADMUX=5;
}

if (ADMUX==3)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00000010;} else {DI_portA_Mask_no_opros &= 0b11111101;}
SREG= savee;
ADMUX=4;
}

if (ADMUX==2)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
kod_acp = (unsigned int) ADC;
if (kod_acp<300) {DI_portA_Mask_no_opros |= 0b00000001;} else {DI_portA_Mask_no_opros &= 0b11111110;}
SREG= savee;
ADMUX=3;
}

if (ADMUX==1)
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
accu2 += (int32_t) ADC;//Оверсемплинг AI2. Было 10 бит, стало 13
accu_count2++;
if (accu_count2>=64)
{
ADC_AI_2=(int16_t)(accu2/8); accu_count2=0; accu2=0;//фильтрация AI2
ADC_AI_2_ff=prom_out2+(ADC_AI_2-prom_out2)/(float)koef_filtra_AI2; //экспоненциальный фильтр
prom_out2=ADC_AI_2_ff;

if (tip_AI2==1)
{
AI_2= ((float)((ADC_AI_2_ff-calibr_ADC_min_AI2)/(calibr_ADC_max_AI2-calibr_ADC_min_AI2)) * (((float)(VPI_AI2-NPI_AI2))/koeff_AI2)+ ((float)(NPI_AI2/koeff_AI2)));//Аппроксимация AI2
AI_2_fv=AI_2*(((float)koef_A_AI2)/1000)+(float)koef_B_AI2/koeff_AI2; //Коррекция AI2
}
else {AI_2=0;AI_2_fv=0;}
}

SREG= savee;
ADMUX=2;
}

if ((ADMUX==0) && (lock_0==0))
{
ADCSRA |= (1<<ADSC); //Начинаем преобразование
while((ADCSRA & (1<<ADSC))) {}; //проверка закончилось ли аналого-цифровое преобразование
savee =SREG;
cli ();
accu1 += (int32_t) ADC; //Оверсемплинг AI1. Было 10 бит, стало 13
accu_count1++;
if (accu_count1>=64)
{
ADC_AI_1=(int16_t)(accu1/8); accu_count1=0; accu1=0;//фильтрация AI1
ADC_AI_1_ff=prom_out1+(ADC_AI_1-prom_out1)/(float)koef_filtra_AI1; //экспоненциальный фильтр
prom_out1=ADC_AI_1_ff;

if ((tip_AI1>0) && (tip_AI1<4))
{
AI_1= ((float)((ADC_AI_1_ff-calibr_ADC_min_AI1)/(calibr_ADC_max_AI1-calibr_ADC_min_AI1)) * (((float)(VPI_AI1-NPI_AI1))/koeff_AI1)+ ((float)(NPI_AI1/koeff_AI1)));//Аппроксимация AI1
AI_1_fv=AI_1*(((float)koef_A_AI1)/1000)+(float)koef_B_AI1/koeff_AI1;//Коррекция AI1

}
else
{AI_1=0;AI_1_fv=0;}
}

SREG= savee;
ADMUX=1;
}
lock_0=0;

}

void indi()
{
static char n_count=1; //Переменная для перебора посылаемых байтов-символов на разряды индикатора

if (n_count==8)
{symboll(R8);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b01111111;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==7)
{ symboll(R7);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b10111111;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==6)
{symboll(R6);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11011111;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==5)
{symboll(R5);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11101111;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==4)
{symboll(R4);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11110111;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==3)
{symboll(R3);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11111011;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==2)
{symboll(R2);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11111101;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

if (n_count==1)
{symboll(R1);
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
SPDR =0b11111110;//выбор индикатора
while (!(SPSR & (1<<SPIF))) {}; //ожидание, пока данные передадутся
//отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<4); // высокий уровень
PORTB &= ~(1<<4); // низкий уровень
}

++n_count;
if (n_count>8) {n_count=1;}

}

int main(void)
{
preset();
SPI_init();
ADC_init();
sei();
while (1)
{

ADC_convert();
R1=7;
R2=7;
R3=7;
R4=7;
R5=7;
R6=7;
R7=7;
R8=7;
indi();
}//КОНЕЦ ОСНОВНОГО ЦИКЛА while
}//КОНЕЦ ОСНОВНОЙ ФУНКЦИИ main


Функция ADC_convert() влияет на работу функции indi(). ADC_convert() - опрос 8 каналов АЦП, indi() - индикация на 8-разрядный семисегментный экран по SPI. Проблема - последний разряд (R8) мерцает, в то время как остальные разряды горят нормально.
Это не вся программа, только те её части, с которыми проблема. Код компилится (Atmel Studio 7) и проблема в нём проявляется. 2 строчки, выделенные жирным цветом - если их закомментировать, то мерцание пропадает. Какое отношение они имеют к индикации - непонятно, но влияют. Такие же две строчки есть и чуть выше в этой же функции,
только с другими переменными работают, - и они почему-то никак не влияют на работу других частей программы.
DASM
calibr_ADC_max_AI2 - calibr_ADC_min_AI2 у вас неинициализированы, а в этих строках вы делите на их разность, оная будет равна 0, так они в стартапе обе обнуляются.
lyric
Цитата(DASM @ Sep 12 2018, 15:28) *
calibr_ADC_max_AI2 - calibr_ADC_min_AI2 у вас неинициализированы, а в этих строках вы делите на их разность, оная будет равна 0, так они в стартапе обе обнуляются.


Все переменные инициализировал, ничего не изменилось.
Ну и прошу прощения, - ошибся:
Присмотрелся лучше к экрану - мерцания прекращаются только если ADC_convert вообще не вызывать.
Что-то не так с этой функцией (подскажите что?). И почему она портит только один разряд а не все 8 - не пойму.

Когда ADMUX=0 и ADMUX=1 функция ADC_convert выполняется дольше, потому что больше расчётов в ней происходит.
Поэтому функция indi() в эти моменты дольше НЕ выполняется.
И, видимо, эти моменты совпадают с моментами, когда должен включаться индикатор (R8).

Но между ADMUX=0 и ADMUX=1 функция indi() всё равно должна успеть выполниться 1 раз, и получается что мерцать должны 2 индикатора, а не один.
Так и есть? или это бред?

help.gif help.gif help.gif
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.