Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
Накидал схемку в ПРОТЕУСе. Файл прошивки дезасемблировал в .asm, что бы получить полноценный проект. Начал сбивать в кучу... Но не тут то было. Исходник получается с ошибкой и процесс создания hex файла проекта не удаётся. Определить характер ошибки к сожалению не могу... А может я на ложном пути???
Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
Спасибо. Так и делал. Только вот не до конца понял, почему, в нарисованной мной схеме индикатор просто скачет, а в переделанной твоей держит нули... Урри, как в этом протеусе изображать вход и испытуемый сигнал? Надо ли точно копировать делитель. А сигнал подавать с генератора, просто DC?
На листе проекта добавляем необходимый элемент POWER или GROUND, но в свойствах (Edit Properties) указываем конкретный потенциал обязательно таким образом: +12 или -0.5V или +0 т.е. начинается со знака и заканчивается цифрой или символом V. Тогда он будет автоматически добавлен в Configure Power Rails…
«Антон Палыч Чехов однажды заметил что умный любит учиться, а дурак учить» (с) Б.Ш. Окуджава По просьбе трудящихся … Это не курс – «си для начинающих» , я просто рассказываю, как я бы сделал я. Компилятор хайтек – он же Hi-Tech – он не без недостатков, но для 10-16 серий- Лучше пока не придумали. Микоконтроллер берем из 16 серии, где больше ножек и ацп ну, скажем 16F877А Итак, первая задача – зажечь индикаторы Создаем пустой текстовый файл с именем main.c В нем пишем
CODE
#include <pic.h>
Пока хватит – лезем в директорию - C:\Program Files\HI-TECH Software\PICC\9.50\include и ищем в ней этот самый файл – pic.h. Раскрываем в блокноте и ищем в нем адрес заголовочного файла для нашего МК. Нашли
Этот файл – pic168xa.h - распечатать на принтере и держать перед глазами до конца проекта, как минимум. Подключать «ручками» к проекту этот файл - #include <pic168xa.h> - не нужно- Мастер проекта сам поймет по имени мк и благодаря этой записи - #include <pic.h> Что Вы хотите . Создаем слово конфигурации для МК –дописываем в main.c
Значение каждого параметра – в конце файла pic168xa.h Выполнение программы начинается и заканчивается в процедуре main, в которой проходит начальная установка параметров и затем включается бесконечный цикл. Обычная запись – «рыба» программы // подключение заголовочных файлов // слово конфигурации // определение глобальных переменных программы // объявление функций, используемых в программе //прерывание void main() // начало процедуры { // установка начальных параметров while(1) { // начало бесконечного цикла }// окончание бесконечного цикла }// окончание процедуры Индикация. Используем 4-х разрядные индикаторы, обновление информации на которых должно происходить комфортно – с частотой, скажем 100 герц на разряд - чтобы не было видно мерцание. Определяемся с частотой кварца – скажем, 20 мегаГерц. Пишем #define fosc 20000000 4 индикатора, каждый по 100 Герц, в сумме 400. Определяем это значение #define fin 400 на таймер 1 пускаем значение без пределителей , клок делить на 4 #define ftm fosc/4.0 Вычисляем значение 16-разрядное таймера #define tm_all (int)(65536 - (ftm/fin)) Не стесняйтесь загружать компилятор вычислениями – в конце –концов, кто верхнее звено пищевой цепочки – Вы или эта железяка ??? Cчитается это только во время компиляции – во время выполнения программы В нее поступают готовые константы. И, наконец, разбиваем 16 –разрядное слово tm_all на 2 8-разрядных #define TMR1H_ (int)tm_all>>8 #define TMR1L_ tm_all&0x00FF В процедуре init() заносим эти значения в таймер TMR1H= TMR1H_; TMR1L= TMR1L_; То же будем делать и в прерывании. Обратите внимание, при перестройке на другую частоту необходимо будет изменить только 1 значение – остальная часть кода не трогается. Для отображения чисел на индикаторы используем порт В, для управления разрядами индикатора – пусть будет порт D – с 0 по 3 ногу. Берем распечатанный лист и смотрим, как оно правильно называется Объявляем
В 4-х разрядный индикатор мы можем занести число от 0000 – 9999 - нам хватит 16 разрядное число (unsigned int – 0000-65535). Но и его нужно перевести в строку из 4 знаков – написать фунцию перевода - на входе 2 байтовое число, на выходе – 4 байта символов. Объявим массив в оперативной памяти из 4 байт Unsigned char arr[4]; И пишем функцию перевода числа в эти 4 байта
CODE
void inttostr(unsigned int tmp) { const unsigned int step[4]={1000,100,10,1}; unsigned char i; unsigned int val,temp,atemp;; val=tmp; for (i=0; i<4; i++) { temp=step[i]; atemp=0; while(val >= temp) { atemp++; val-=temp; } arr[i]=atemp; } }
Передаем значение в эту функцию так inttostr(987); - где 987 – это число, которое необходимо высветить После выполнения этой функции содержимое массива arr будет следующим Arr[0] - 0 Arr[1] - 9 Arr[2] - 8 Arr[3] - 7 Как подать, скажем, первое число на индикаторы ? 1.Отключаем вывод индикаторов 2. Достаем из массива значение по 0 адресу - в данном случае- 0 3. Достаем из массива ПЗУ по адресу 0 – «образ” числа 0 – какие сегменты в нем должны гореть – и посылаем его в порт В 4.Включаем выход для 1 разряда индикаторов 5 . Уходим из прерывания Для второго числа 1.Отключаем вывод индикаторов 2. Достаем из массива значение по 1 адресу - в данном случае- 9 3. Достаем из массива ПЗУ – по адресу 9 «образ” числа 9 – какие сегменты в нем должны гореть – и посылаем его в порт В 4. Включаем выход для 2 разряда индикаторов 5 . Уходим из прерывания И так по кругу с частотой 400 Герц – используем счетчик до 4, объявим его где-то В глобальных переменных и дадим спецификатор volatile, чтобы компилятор не вздумал в каком-либо куске модифицировать его по своему вкусу. Далее, нам необходим еще 1 массив – массив знакогенератора , который не будет меняться – и расположим его в области памяти программ. Содержимое этого массива – зависит от того, какие ножки на какие сегменты индикаторов навешаны. Например, вывод числа 1 – сегменты в с – если у нас на ножке B0 – не используется 0 в1 – сегмент а 0 в2 – сегмент b 1 в3 – сегмент c 1 в4 – сегмент d 0 в5 – сегмент e 0 в6 – сегмент f 0 в7 – сегмент g 0 и индикаторы с общим катодом – то есть засветка индикатора – выводом 1 00110000 - так будет выглядеть число – в шестнадцатеричной 0x0C <- младшие разряды Записываем
Квалификатор const объясняет, что его место – в памяти программ. Итак – прерывание – регенерация дисплея полностью
CODE
Volatile unsigned char count_r; interrupt void tajm(void)// начало обработки прерывания { unsigned char temp; // внимание – локальная переменная – разрушается при выходе из функции // в отличии от глобальных //---------------------------------------- if (TMR1IF& TMR1IE) { TMR1H= TMR1H_; TMR1L= TMR1L_; TMR1IF=0; switch (count_r) { case 0: // для 1 разряда r4=0;// выключили 4 разряд temp=arr[count_r]; // получили значение по 0 адресу PORTB=arr1[temp]; // получили образ числа и выдали на индикаторы r1=1; // включили 1 разряд break; // ушли case 1: r1=0; temp=arr[count_r]; PORTB=arr1[temp]; r2=1; break; case 2: r2=0; temp=arr[count_r]; PORTB=arr1[temp]; r3=1; break; case 3: r3=0; temp=arr[count_r]; PORTB=arr1[temp]; r4=1; break; } count_r++; // добавили счетчик разрядов if (count_r == 4) count_r=0; // если крайнее значение, обнуляем счетчик } // выход обработки таймера }// выход прерывания
Теперь – через мастер проекта создаем проект, компилим, смотрим в протеусе, подключив cof файл к мк. Исходники – в приложении.
Пока все – индикаторы засветили – если будет смысл продолжать – продолжу, вытрет модератор- ничего страшного…
Группа: Cоучастник
Сообщений: 743
Пользователь №: 17855
Регистрация: 15-February 07
Место жительства: Москва
Поддерживаю vanish. Тема интересная и популярно излагается. Но к сожалению насколько я знаю PIC16F628 без АЦП и вольтметр не получится, а вот к примеру если можно так же описать как сделать частотомер на 628 для своих нужд и со своим прескалером это бы многим помогло на форуме.
vanish, в качестве домашнего задания сделайте сами. Что для этого нужно - отключить компараторы в функции инициализации командой CMCON=7; , заремить там же неиспользуемые порты (которых нет в 628)
Очень не вредно найти заголовочный файл для 628 и почитать литературу на русском языке http://www.microchip.ru/lit/?mid=1x0 Сегодня говорим о функциях временных задержек. К сожалению, находящиеся в папке "самплы" функции delay.c delay.h написаны с ошибкой. ИХ НЕ ИСПОЛЬЗОВАТЬ !!! Это не единственная ошибка, к сожалению, которая находится в самлах и которую хайтековцы с постоянством, достойным лучшего применения, тянут из версии в версию. Что же использовать ? Те функции, которые я приложил в проект - это не мое изобретение - я их нашел где-то на просторах инета. Создаем новый проект, в папку с проектом бросаем уже 3 файла - main.c delay.c delay.h. Подключаем хидер ( заголовочный файл) в файл main.c
CODE
#include "delay.h"
Кавычки вместо угловых скобок говорят компилятору о том, что этот файл нужно искать в директории с проектом. Объясняем (грубо, по-мужски) - с какой частотой кварца этому файлу придется иметь дело - добавляем в файл delay.h строчку
CODE
#define PIC_CLK 20000000
Это не единственный способ объяснить. Можно создать файл main.h, в котором написать #define fosc 20000000 // 20 мегагерц тактовая затем включить этот файл в проект, затем в main.c
CODE
#include "main.h"
И дописать в delay.h строчки
CODE
#include "main.h" #define PIC_CLK fosc
Все это ради того, чтобы при изменении частоты нужно было менять только 1 строку. Это важно. и применим функцию временной задержки в проекте Добавим глобальную переменную count, при инциализации ее обнулим и в бесконечном цикле будем увеличивать с периодом 1 секунда.
CODE
void main() // начало процедуры { // установка начальных параметров init_();
while(1) { // начало бесконечного цикла inttostr(count); // выводим число на экран count++; DelayS(1); // CLRWDT(); // обнуляем сторожевой таймер }// окончание бесконечного цикла }// окончание процедуры
Обнуление сторожевого таймера заремим - этим занимается сама программа временной задержки.
urry Классно обьясняешь.Может статью оформишь? Скажи плиз какую версию Hi-Tech использовать,ну и где скачать с таблеткой? Да, и как к МПЛАБу прикрутить?
Ответил в личку - мне уже делали замечание за излишнюю "популяризацию" продукта. файл с русским описанием компилятора http://depositfiles.com/files/htre0ft9l . Следующий ответ -уже сам вольтметр - в связи с загрузкой по работе опубликую на днях.
Вольтметр - АЦП выдает данные в 2 регистрах, которые, к тому же находятся в разных банках памяти. Для того, чтобы сбить их в одно 16 - разрядное число, существует несколько способов. Первый вариант -
CODE
unsigned int summa; summa= ADRESH<<8; summa +=ADRESL;
Чистить переменную summa перед таким действом совсем не нужно - во время "сдвига" - компилятор на самом деле ничего никуда не сдвигает, он достаточно понятлив, чтобы сразу бросить ADRESH в старший байт и обнулить младший. Недостаток этого варианта в том, что нам иногда приходится сразу же делать обратное преобразование - 16 - разрядное число на 2 байта - скажем, для выдачи по и2с или в ком порт. Второй вариант -через указатели
CODE
unsigned char arr2[2]; unsigned int summa; arr2[0]=ADRESL; arr2[1]=ADRESH; summa = * (unsigned int*)&arr2;
&arr2 равнозначно записи &arr2[0] - адрес нулевой ячейки структуры. В данном случае мы загружаем в 16 -разрядное число summa содержимое массива arr2, причем указываем компилятору, что оно 2 - байтовое (unsigned int)- т.е грузим содержимое ячеек 0 и 1. Если чисел несколько, то можно их все писать последовательно, увеличив массив, а выбор нужной - по индексу Если у нас в массиве ячейки 2 и 3 заняты другим числом, то достучаться к ним можно, чуть изменив формулировку
CODE
summa = * (unsigned int*)&arr2[2];
Нужно обратить внимание на формат чисел - big-endian и little-endian формат - где располагается младший байт - по старшему или по младшему адресу. У хайтека - младший - по младшему - little-endian. Третий вариант - через юнионы и структуры - наиболее универсальный и красивый
CODE
union { struct { unsigned char lo; unsigned char hi; }bt; unsigned int all; }un;
Обращение в этом случае к 16 - разрядному числу all будет так - un.all=0x1234; К старшим и младшим байтам соответственно un.bt.hi =0x56; un.bt.lo =0x78; Универсальность заключается в том, что не нужно объявлять их много, если требуется обращаться к десятку переменный - объявить их массивом - например, в случае массива из 10 двухбайтовых слов
CODE
typedef union { struct { unsigned char lo; unsigned char hi; }bt; unsigned int all; }un; un array[10];
обращение, например, к 0 элементу массива array[0].all=0x1234; И соответственно по байтам array[0].bt.hi =0x56; //--------------------------------------------------------------- Cледующий вопрос - суммирование (усреднение ) результатов выборок АЦП. Первый вариант - простое (арифметическое) суммирование - берем сумму, скажем, из 256 выборок и делим на 256. К недостаткам можно отнести - если несколько выборок сильно отличается от предыдущих - больше погрешность. К примеру, берем 4 выборки - 4, 4, 4, 10 В сумме 22/4 = 5.5 //------------------- Второй вариант - последовательное суммирование с предыдущим результатом - программный ФНЧ Те же выборки 1. 4 2.(4+4)/2 =4 3.(4+4)/2=4 4.(4+10)/2=7.5 (4+4+4+7.5)/4 = 4.87 //-------------------- Как видим, второй вариант точнее. Из своего опыта - применение такого "программного" ФНЧ не освобождает от применения "железного" ФНЧ на входе АЦП. Это связано с тем, что когда во время измерения (эти несколько микросекунд) из-за шумов напряжение на входе АЦП меняется - выходной результат самый непредсказуемый. В любом случае, когда, даже если кажется, что шумам взяться просто неоткуда - применяйте ФНЧ - резистор с емкостью на входе АЦП , не ошибетесь. "Береженого бог бережет" - как говорила монашка,... Пример
CODE
typedef union { struct { unsigned char lo; unsigned char hi; }st; unsigned int all; }tun; tun t; //------------------------------------------------------- unsigned int adc_sum(void) { unsigned int z=0;// переменная для суммы unsigned int z_old;// переменная для старого значения unsigned char i; for(i=0;i<64;i++) // усредняем 64 выборки { ADGO=1;//запускаем преобразование while (!f_adc) continue;//ждем завершения приобразования t.st.hi=ADRESH; t.st.lo=ADRESL; if (i == 0) { z_old=t.all;// первое значение отправляем в сумму z=z_old; } else { z_old+=t.all; // сложили текущее значение со старым z_old/=2; // поделили на 2 z+=z_old; // просуммировали } // сложили 64 раза DelayUs(5); } z/=64;// cумму поделили на 64 return z; // вернули усредненный вариант для индикации }
Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
Читаю и понимаю, что нихрена не понимаю )
urry, ликбез РЕСПЕКТ! Считаю, что его необходимо определить в отдельный топ и несомненно развивать!!! Тема актуальная. Сегодня же попрошу модератора этого раздела сделать рассечение.
Возвращаясь к теме вольтметра на 676. urry, пожалуйста помоги, мне необходимо перевести сей девайс на миливольтовый режим (до 500mV). Реально ли это малой кровью?
<b>Luk</b> относительно ликбеза - Вы уже высказались .. Что ничего не понятно... Тема у меня отбирает время - если я помню, к примеру, что опорное напряжение для ацп не должно быть меньше 2 с копейками вольт - то сразу у меня возникает вопрос - а где я это прочитал ??? Лезу в шит и нигде этого не вижу... Но это я же не придумал, где-то написано... Полез на сайт микрочипа... Время сносится, к сожалению... каждую операцию проверяешь, прежде чем запостить :( Теперь по Вашему вопросу - минимальное значение градации напряжения будет (vref+ - vref-)/1024. Другими словами, при опорном напряжении 2.5 Вольт градация измерения будет достигать около 2 миливольт. Учитывая ошибку как 1 младший разряд - то получается реально около 4 миливольт. Так и это напряжение - опорное - 2.5 Вольт нужно выставить с точностью до 2 миливольт, что тоже та еще зубная боль...
Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
urry, по ликбезу я высказал лишь своё мнение и поверь с таким сожалением упущенного мной в этой жизни... А вот ребятам, которые сделали первые шаги к этому не через клонирование готовых прошив, этот материал будет весьма полезен!!! А мне наверное осилить не судьба уже. Хотя... А вдруг прозрение настанет ...
По вольтметру. Похоже, что эту байду легче было всё же на КР572ПВ2 собрать... Ну или просто добавить к схеме на 767 токовый усилитель или что то вроде этого. Вобщем так, как это организовано у коллеги из Запорожья, LM358.
И всё же тема ликбеза РЕСПЕКТ!
Это сообщение отредактировал Luk - Jan 26 2009, 08:00 PM
urry респект. не думаю, что кто-то будет делать себе вольтметр на пике, а вот как дополнительную функцию (для контроля питания в приемнике) я до сего момента ленился прикручивать; теперь почти готово на халяву. P.S. там в пике встроеный источник опорного напряжения есть (со своими погрешностями), а для 18 пиков есть HT-PICC18
Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
По теме. В прикреплённом архиве лежат две печаточки под smd & DIP PIC16F676. Делитель, вернее место на плате под него, вы можете заполнять на "свой вкус".
Это сообщение отредактировал Luk - Jan 26 2009, 09:30 PM
Не знаю, нужно ли комментировать код дальше - как-то вопросов по коду нет, так что тему закрываю. В приложении - вольтметр в 2 вариантах - для индикаторов с общим кадодом и общим анодом. Точность -10 миливольт. О точности напряжения опоры - я пошел следующим путем - не опору строить под программу, а программу под то опорное, что у нас получилость. Точность выдачи выходного напряжения стабилитроном от 10 до 5 процентов, что в нашем случае неприемлемо. Но ... То напряжение, которое у нас получилось на стабилитроне , со всеми его неточностями можно подставить в формулу в самой программе как константу, измерив его точным высокоомным прибором. Например, в этой схеме я завел вход Vref+ на питание, измерил его - у меня получилось 5050 милливольт. Записываю это значение в константу и компилирую программу под эту константу. Естественно, при замене стабилитрона программу нужно будет перешивать, ну и что ?
CODE
#define vref_p 5050 // в миливольтах #define vref_m 0 #define lsb (vref_p -vref_m)/1024.0 - младший разряд
вычисляем
CODE
double rez;
count= adc_sum();
if (count != 0)count++;
rez=lsb*count;
count=(double)rez; count1= count/10; if ((count - count1*10)>5) { count1++; } inttostr(count1); // выводим число на экран
Схему с индикаторами под общий анод спаял в железе и сверил с показаниями китайского тестера. При повторе не забывать ставить ограничительные резисторы на сегменты ( в дизайне протеуса их нет).
В разных версиях протеуса. Прикрепляю файл, где схемы для вариантов индикаторов с общим катодом и общим анодом. Постройте в протеусе то же самое и прикрепите соответствующие файлы сof или hex из проектов.
Группа: Автор
Сообщений: 5108
Пользователь №: 1407
Регистрация: 16-September 05
Место жительства: www.contrspy.com
urry, всё проще оказалось... Я открывал из архива, а надо было просто извлечь проекты из архива, а потом запускать их. Видимо дело было в длине пути к файлам прошивки . В данном случае версия протеуса наверное совпала ( раз проект открылся) 7.4 SP3.
Вопрос про 10 битное ацп в 87х сериях pic хочу сделать бп для домашней лаборатории, как померить напряжение от 0 до 30 вольт точней если можно описалово в краце а главное формулу пересчота просто я пока не доганяю как выборки ацп пересчитать в вольты вывести на лсд и поставить правильно запятую.
Переделал программу и схему под индикаторы ТОТ3361(строенный с ОК), общий катод, немного изменена разводка сегментов в пользу удобной разводки, плата в PCAD2004, проц в планарном корпусе, схема в протеусе. Пришлось добавить один инвертор(74HC1G04) на управление точкой, мож кто по другому предложит. предел до 30В(предел до 100В не переделал еще, нету знаний, учусь)
В продолжении темы: исправленная прошивка и схема для предела 100В (реально 99,9). Но есть вопрос к знающим: на верхнем значении 99,9 индикатор выкидывает показания 80.0, при задаче напрруги 99,8 , на индикаторе 99,9( на входе мк 5,00В)...или не париться??
urry Спасибо очень интерестно и доходчиво обьяснили, но последний момент без коментариев не много не понятен
CODE
count= adc_sum();
if (count != 0)count++; // зачем инкрементировать значение? для устранения ошибки? rez=lsb*count; //
count=(double)rez; // эту строку не пойму, как я понимаю переменную rez приводим к типу doubl , она ведь такая и есть count1= count/10; if ((count - count1*10)>5) { count1++; } inttostr(count1); // выводим число на экран
и пару вопросов по компилятору, у меня HI-Tech не считает сам
CODE
#define fosc 4000000 // 4 мегагерц тактовая #define fin 4000 // 4 по 100 герц на индикаторы #define ftm fosc/4.0 #define tm_all 55536 //(int)(65536 - (ftm/fin))