Вольтметр/амперметр для лабораторного БП


В ходе размышлений над проектом лабораторного блока питания решил я немного поизучать возможности AVR в качестве измерителя напряжения и тока. Как известно, контроллеры серии ATMega имеют 10-разрядный АЦП, который можно использовать для измерения.

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

Но на этом не заканчивается, в дело вступают проблемы программного характера: не всегда полученное количество отсчетов АЦП можно непосредственно вывести на экран, вступают в силу ошибки накопления, округления и прочие.

Насколько возможно, я пытался уйти от всех этих проблем и сохранить простоту схемного решения. Заодно в программировании поупражняться. Конечно, это только макет, но он определяет приоритеты в конструировании готового прибора.

Итак, что же мы имеем?


Примечание к схеме: на каждый вывод порта B нужно привесить резистор, у меня 680 Ом.

Прибор собран на контроллере ATMega8 в корпусе TQFP-32, это дает нам 2 дополнительных входа АЦП. Обвеса минимум, буквально один блокировочный конденсатор по питанию. Тактирование — внутренняя RC-цепочка на 8 Мгц.

Диапазон измерения напряжения, который я хотел получить — 30В, а тока — 5А. Для входа напряжения применил делитель на резисторах, для входа тока подключил напрямую, в готовой схеме напряжение с шунта будет усиливаться операционным усилителем с таким расчетом, что 5А будет соответствовать 5В на выход. Сейчас вход тока просто открыт, можно мерять напряжение до 5В.

Результаты измерений отображаются на семисегментных индикаторах — 3 разряда напряжения и 4 разряда тока. Совершенно случайно получилось, что зеленый индикатор с общим анодом, а красный — с общим катодом. Вначале я долго пытался приспособить какую-нибудь готовую универсальную библиотеку для семисегментников, потом пытался написать свою, тоже универсальную, в итоге, провозившись неделю и не приблизившись к цели ни на шаг, бросил и написал за два часа быдлокод, сразу все заработало.
Принцип в том, что гасятся все разряды выводом в порт не нулей или единиц, а маски, соответствующей подключению разрядов, включаются разряды инвертированием соответствующего бита, а сегменты в случае общего анода надо инвертировать. Это немного утяжелило восприятие кода, но работает.

Вторая задача, которую пришлось решать — источник опорного напряжения. AVR позволяет использовать 3 источника — напряжение питания, встроенный ИОН 2,56В или внешний AREF. Напряжение питания показалось мне недостаточно стабильным, а встроенный ИОН — маловатым, для измерения 30В будет слишком большой коэффициент деления. К тому же была информация о недостаточной точности этого источника.

Для упрощения расчетов я решил сделать простой ИОН на 5.12В на TL431.

Точное значение опорного напряжения устанавливается многооборотным подстроечником R2, делитель для измерения напряжения тоже дополнен подстроечником R6, потому что точных резисторов необходимого номинала у меня нет. Если у вас есть, вы можете пересчитать под другие номиналы. К тому же, в результате борьбы за точность я изменил максимальное входное напряжение до 32В, так что подстроить оказалось легче, чем перепаивать.

Теперь перейдем к программированию. Здесь есть две задачи: перейти от отсчетов АЦП к вольтам и повысить точность измерения.

После нескольких вариантов пересчета я остановился на следующем.
Задаемся максимальным выходным значением 30.0В, десятичную запятую выкидываем, получается 300. Записываем степени двойки до максимального значащего разряда нашего АЦП и смотрим, какие степени в сумме составляют наше число: 256+32+8+4==300. Теперь, чтобы получить весь диапазон, нужно сделать: value=(ADC>>1)+(ADC>>4)+(ADC>>6)+(ADC>>7);
Удобно для этого пользоваться каким-нибудь табличным редактором, у меня, например, OpenOffice Calc:


Вот тут-то и вылазят все ошибки точности и округления.

Для повышения точности хорошо бы применить программные методы, например оверсемплинг, потому что 10 бит недостаточно даже для одной цифры после запятой в диапазоне 0-30В.
Для того, чтобы поднять разрядность на N бит, нужно сделать 4^N измерений и затем сумму этих измерений поделить на 2^N, то есть сдвинуть вправо N раз.
По-хорошему, нужно добавить еще шум, но я пока решил обойтись без этого.
Код:
//ANTONLUBA 2013

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include "bits_macros.h"

uint8_t segment[] = 
		//pgfedcba
		{
		0b00111111,     //0
		0b00000110,	//1
		0b01011011,	//2
		0b01001111,	//3
		0b01100110,	//4
		0b01101101,     //5
		0b01111101,	//6
		0b00000111,	//7
		0b01111111,     //8
		0b01101111	//9
		};

//Сегменты
#define PORT_IND PORTB	
#define DDR_IND DDRB

//Цифры
#define PORT_DRV PORTD	
#define DDR_DRV DDRD
	
//#define CREFERENCE (unsigned int) 5120
//#define VREFERENCE (unsigned int) 2400

//#define COMMA_POS0 1  //позиция точки вольтметра (сделана переменной)
#define COMMA_POS1 8    //позиция точки амперметра 

uint8_t digits[10];// = {6,5,4,3,2,1,0); // буфер для разрядов
uint8_t current_digit, // текущая цифра
		current_num,	//индикатор
		current_chan,	//канал АЦП
		comma_pos0;		//позиция точки вольтметра
unsigned int adc_counter1, adc_counter2; //счетчики суммирований значений АЦП
unsigned long int value1, value2;        //значения сумм АЦП

void BIN2BCD(uint8_t *buffer, unsigned int n)
{
	//int n;
	//n=number;
	buffer[4]=0;
	while(n>=10000) {buffer[4]++;n-=10000;}
	buffer[3]=0;
	while(n>=1000) {buffer[3]++;n-=1000;}
	buffer[2]=0;
	while(n>=100) {buffer[2]++;n-=100;}
	buffer[1]=0;
	while(n>=10) {buffer[1]++;n-=10;}
	buffer[0]=n;
}

void BIN2BCDslow(uint8_t *buffer, int n)
{
uint8_t a0,a1,a2,a3, b0,b1,b2,b3,b4;
a0= (uint8_t)(n&0x0F);
a1= (uint8_t)((n&0xF0)>>4);
a2= (uint8_t)((n&0x0F00)>>8);
a3= (uint8_t)((n&0xF000)>>12);
b0=a0-4*(a3+a2+a1)-20;
b1=6*a2+2*a1-138;
b2=a3+2*a2-46;
b3=4*a3-64;
b4= 7;
while (b0>10)
	{
	b0+=10;
	b1--;
	}
while (b1>10)
	{
	b1+=10;
	b2--;
	}
while (b2>10)
	{
	b2+=10;
	b3--;
	}
while (b3>10)
	{
	b3+=10;
	b4--;
	}
buffer[0]=(uint8_t)b0;
buffer[1]=(uint8_t)b1;
buffer[2]=(uint8_t)b2;
buffer[3]=(uint8_t)b3;
buffer[4]=(uint8_t)b4;
}



int main (void)
{
//Инит портов
PORT_IND=0xFF;
DDR_IND=0xFF;

PORT_DRV=0x00;
DDR_DRV=0xFF;

//Запустить таймер
TCCR0 = (1<<CS01)|(1<<CS00);
TIMSK = (1<<TOIE0);

//Запустить ADC
ADMUX = (0<<REFS1)|(0<<REFS0)|(0<<ADLAR)|(0<<MUX3)|(1<<MUX2)|(1<<MUX1)|(0<<MUX0);
ADCSRA = (1<<ADEN)|(1<<ADSC)|(0<<ADFR)|(0<<ADIF)|(1<<ADIE)|(1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0);

sei();		//вкл прерывания
while (1)
	{
	}
}


ISR(TIMER0_OVF_vect)  //переполнение таймера
{
if (current_num==0)		//индикатор 0 - вольтметр
	{
	//вывести цифру
	PORT_DRV=0b01111000;	//маска общий катод == 1
	
	if ((current_digit>comma_pos0)&&(digits[current_digit]==0)
                                    &&(digits[current_digit+1]==0))
		{
		PORT_IND=0xFF; //выключить цифры левее старшего 0 
		}
	else
		{
		PORT_IND=~segment[digits[current_digit]]; //отобразить цифру
		}
	if (current_digit==comma_pos0)			//если позиция точки
		{
		ClearBit(PORT_IND, 7);			//вывести точку
		}
	SetBit(PORT_DRV, current_digit);		//включить разряд
	current_digit++;			//увеличить номер текущего разряда
	if (current_digit==3)				//если вышли за пределы
		{
		current_digit=0;	         	//обнулить номер разряда
		current_num=1;					//индикатор1 
		}
	}
else
	{
	//вывести цифру
	PORT_DRV=0b01111000;				//маска общий катод == 1
	
	if ((current_digit+5>COMMA_POS1)&&(digits[current_digit+5]==0)
                                        &&(digits[current_digit+5+1]==0))
		{
		PORT_IND=0x00;		//выключить цифры левее старшего 0 
		}
	else
		{
		PORT_IND=segment[digits[current_digit+5]];//отобразить цифру
		}
	if (current_digit+5==COMMA_POS1)		//если позиция точки
		{
		SetBit(PORT_IND, 7);			//вывести точку
		}
	ClearBit(PORT_DRV,(current_digit+3));//включить разряд, 3 == ширина вольтметра
	
	current_digit++;			//увеличить номер текущего разряда
	if (current_digit==4)			//если вышли за пределы
		{
		current_digit=0;		//обнулить номер разряда
		current_num=0;				//индикатор0
		}
	}
}

ISR(ADC_vect)		//АЦП преобразование выполнено
{
unsigned long int temp;
if (current_chan==0)
	{
		//напряжение
		if (adc_counter1<1024)  //15 значащих разрядов в 20-битном числе
			{			//пока не сложим 1024 раза
			value1+=ADC;		// складываем
			adc_counter1++;		//ув. счетчик
			}
		else				//если достигли
			{			//сдвигаем на 5 разрядов
			value1=value1>>5; //15bit
			// и приводим к нужному значению
			temp =((value1>>4)+(value1>>6))>>3; //2560/8 = 320
			comma_pos0 = 1;
			if (temp<=99)	//если меньше 10В
						// приводим к другому значению
				{temp = ((value1>>4)+(value1>>5)+(value1>>8)); //3200
				comma_pos0 = 2; //и передвигаем точку
				}
			BIN2BCD(&*digits, (unsigned int)temp); //преобразуем в код
			adc_counter1=0; 		// сбрасываем счетчик
			value1=0;			// и значение
			}
		SetBit(ADMUX,MUX0);			//переключаем канал АЦП
		current_chan=1;					//и переменную
		SetBit(ADCSRA, ADSC);			//старт преобразования
	
	}
else
	{
		//ток			//здесь почти то же, только 16 значащих бит
		if (adc_counter2<4096) //в 24-битном числе
			{
			value2+=ADC;
			adc_counter2++;
			}
		else
			{
			value2=(value2>>6); //16 bit
			temp = (((unsigned int)value2>>1)+((unsigned int) value2>>3))>>3;
			BIN2BCD(&*digits+5, (unsigned int)temp);
			adc_counter2=0;
			value2=0;
			}
		ClearBit(ADMUX, MUX0);
		current_chan=0;
		SetBit(ADCSRA, ADSC);
	}

}


Ну и напоследок, несколько фото:
Напряжение, больше 10В:

Напряжение, меньше 10В:

Ток:

Снизу:


В аттаче схема и модель для протеуса, исходник и готовая прошивка (подсунуть протеусу).

PS: кстати, в исходнике есть функция BCDSlow. Она, по-идее, должна быть BCDfast, но на малых значениях проигрывает обычному вычитанию. Я ее переводил с пиковского ассемблера, там она получается быстрая, по сути, все делается сдвигами и сложениями и в циклах получается не больше 9 итераций на каждый десятичный разряд, но на С что-то не заладилось. Можете глянуть на предмет оптимизации.
Файлы в топике: Model_sch.zip, Firmware.zip

Комментарии (43)

RSS свернуть / развернуть
Прибор собран на контроллере ATMega8 в корпусе TQFP-32, это дает нам 2 дополнительных входа АЦП.
А смысл? Все равно ADC0-ADC5 не задействованы. Лучше уж с них сэмплировать, чтобы прошивка была совместима с DIP28-версией.
Для того, чтобы поднять разрядность на N бит, нужно сделать 4^N измерений и затем сумму этих измерений поделить на 2^N
По моему, нужно все же 2^N измерений.
0
  • avatar
  • Vga
  • 05 июня 2013, 19:21
Кажется, вот отсюда
0
А, да, все правильно. Это меня сглючило, сорри.
0
Усредняя n отсчётов, мы сужаем шумовую полосу в n раз, а напряжение шума уменьшится в  раз.
Но эта метода работает для равномерно распределённого, некоррелированного шума.
0
А смысл? Все равно ADC0-ADC5 не задействованы.
Подключится еще куча светодиодов и реле, скорее всего все будут заняты, а 6-7 канал только как вход АЦП можно использовать.
0
Если хочешь высокую точность АЦП — лучше к порту C ничего, кроме аналоговых (ну, еще, возможно, цифровых) входов не цеплять. Если GPIO не хватает — можно мультиплексировать. Еще можно 74HC595 поставить на сегменты или (пожалуй, этот вариант даже лучше) разряды индикатора.
Впрочем, если не ориентироваться на повторение схемы другими, предпочитающими DIP — то и ADC6/ADC7 ничем не хуже.
0
На разряды я бы предпочел декодер 3-to-8 типа 74HC238, но вот разный тип индикаторов все портит.
0
Ну, 74HC595 — тоже, в некотором роде, 3-to-8, только несколько гибче :)
0
потому что 10 бит недостаточно даже для одной цифры после запятой в диапазоне 0-30В.

Как так?

octave:1> 30/1024
ans = 0.029297

около 3 сотых вольта на один разряд ацп.

Пересчет можно и через умножение сделать, про быстродействие же речи не идет? Да и аппаратное умножение есть.
+2
попробую так сделать
0
Попробовал. Сильно дрожит младший разряд, не остановишь. Кажется, это было одной из причин, почему я в оверсемплинг полез.
-1
Сильно дрожит младший разряд, не остановишь
Фильтрация сигнала с АЦП не помогает?
Кстати, у тебя на схеме не хватает буферных конденсаторов на входах АЦП.
+1
На схеме ничего нет, в реале, конечно, есть, без них дрожит до половины значащих разрядов)))
Но все равно макетка плохая, а может еще в чем-то дело, когда в одном канале увеличиваешь напряжение, в другом немножко тоже растет.
0
Ну и, кстати, если по нормальному, надо тогда BIN2BCD делать для типа float с полноценной десятичной точкой, я даже не представляю, как это.
0
я делал проще и с точкой или точками
kalobyte.com/7-seg-dinamicheskaya-indikaciya
0
Ага, (digit|80). Там вообще много чего переделать надо. Под полноценной я имел в виду, чтобы функция преобразовывала тип float в набор десятичных цифр с точкой, что-то типа ftoa(), только в сегменты.
0
слишком много ресурсов жрать будет
поэтому я не стал заморачиваться так глубоко
0
AVR121: Enhancing ADC resolution by
oversampling


For each additional bit of resolution, n, the signal must be oversampled four times.


Thescale factor, sf, given by Equation 3-2, is the factor, which the sum of 4n samples should be divided by, to scale the result properly. n is the desired number of extra bit.
Equation 3-2: sf = 2^n


На каждый дополнительный разряд АЦП надо сделать 4^n лишних семплов. После суммирования оверсемплов результат надо побитно сдвинуть вправо n раз (разделить на 2^n).
0
А я разве не так написал?
0
Та пока написал, то уже и ваш коммент появился.
0
Подробно не просматривал (времени нет), но первое, что бросилось в глаза — надо бы входы АЦП зашунтировать конденсаторами…
0
  • avatar
  • SWG
  • 06 июня 2013, 11:16
Ведь измерения проводятся только для индикации? Тогда нет смысла обновлять показания чаще нескольких раз в секунду.
Склеить бы ФНЧ с частотой среза ~10 Гц, хотя бы RC первого порядка, тогда и посмотреть, быть может и оверсэмплинг с дизерингом (тьфу ты, мерзопакость какая) не понадобятся.
+1
Надо попробовать, но, кажется, два младших разряда все равно пляшут, даже если закоротить на землю.
0
Два разряда — это вроде бы как бы немного, но больше, чем должно быть. Интересно бы записать большой объём измерений и провести частотный анализ.
0
обновлять показания чаще нескольких раз в секунду.
показания тока примерно 8 раз в секунду обновляются (на глаз), напряжение чаще, как раз из-за оверсемплинга.
0
Да есть там конденсаторы, выше писал.
0
Автор говорит, что хотя на схеме их нет — на макетке они есть.
Ведь измерения проводятся только для индикации? Тогда нет смысла обновлять показания чаще нескольких раз в секунду.
Если ты о конденсаторах — то они необходимы для сглаживания бросков входного тока АЦП в моменты сэмплирования.
0
Имеется в виду входной ток зарядки конденсатора УВХ? По-хорошему, там должен быть свой входной буфер. Если УВХ встроено, то разработчик должен был буфер предусмотреть.
Но я не (с)только об этом, я говорил о кондиционировании именно сигнала.
0
Буфера нет:
When the channel is selected, the source must drive the S/H capacitor through the series resistance (combined resistance in the input path).
The ADC is optimized for analog signals with an output impedance of approximately 10 k or less. If such a source is used, the sampling time will be negligible. <...> The user is recommended to only use low impedant sources with slowly varying signals, since this minimizes the required charge transfer to the S/H capacitor.
ATmega8_L_datasheet.pdf, p. 195.
0
Вот и я о том. В кондиционировании сигнала тоже не так много толку — конечно, выше Fд/2 резать надо, но от шума АЦП это не спасет. Расширять диапазон АЦП для трех разрядов незачем (разве что если калибровка программная), но вот шум давить придется — без этого можно на AVR только на 8 разрядов рассчитывать.
0
Производитель декларирует:
• 10-bit Resolution;
• 0.5 LSB Integral Non-linearity;
• ±2 LSB Absolute Accuracy.
О шуме ничего не сказано. Какая конкретная информация есть у вас о шуме АЦП Atmega-8? Почему вы полагаете, что два разряда можно выбросить в мусорку? Это такой собственный шум?
На схеме я вижу входные для АЦП цепи как раз порядка 10 килоом, нормально, можно сделать меньше, сделать простейшую аналоговую фильтрацию. Было бы три полных десятичных разряда, как раз и понадобилось бы десять бит.
Да, у Atmega ещё есть такой интересный режим как ADC Noise Canceler.
0
Со слов DIHALT'а и подобных источников в интернете. ОБС, короче.
0
Такое мнение имеет право быть.
Если и взаправду дело обстоит именно так, что больше восьми бит не высосешь, и нужна только лишь индикация, и нужен результат, а не процесс, то я вношу предложение тов. antonluba привинтить что‑нибудь навроде двух КР572ПВ2/ПВ5.
0
Зачем?
Думаю, дешевле и проще точно не получится, а полученная точность меня вполне устраивает.
0
В младших битах — вполне белый шум, AFAIK, его можно отрезать фильтром и таки получить свои 10 бит.
0
Если это внутренний шум АЦП, его принципиально никак невозможно отрезать никаким внешним фильтром, но вот oversampling здесь будет — самое то, и dithering не понадобится, и некоррелированные внешние помехи тоже подрежутся.
0
Я о том же.
0
Привет всем.
Интересный вольт-амперметр.
Может добавить режим ADC Noise Reduction?
Хорошо бы кнопку добавить для программной коррекции смещения нуля ОУ. Хороший результат у меня получался с AD8552.
Оба канала хорошо бы сделать двухдиапазонными Амперметр 0-999,9 ма, 1,000 — 9,999А, вольтметр 0- 9,999, 1,000 — 30,00.
Переключения каналов не сложно реализовать. В канале Амп. стоит 2 ОУ с разным усилением (AD0, AD1) Вольтметр — два делителя на (AD2,AD3). Логика проста: если значение AD1, например, меньше нижнего значения «Предел», то выводим AD0.
Могу привести код.
0
Может, статью напишете?
0
Я не такой профи, что бы суметь написать.
Но, из личного опыта построения и использования показометра знаю, что надо добавить, что бы получился «Народный Показометр».
Могу привести код (не моя разработка) двух-диапазонного амперметра + вольтметр на меге 8. Оттуда можно применить кусок кода выбора канала ADC.
У вас классно реализован оверсемплинг, 4 разряда,… добавить бы еще несколько плюшек.
0
'* Filename: Вольтметр, 2х диапазонный ампертметр *
'* Revision: 5.1 *
'* Controller: ATMEGA8 *
'* Compiler: BASCOM-AVR 2.0.6.1 *
'* Author: MACTEPOK *
'*******************************************************************************
$regfile = «m8def.dat» 'определяем контроллер
$crystal = 8000000 'внутренний генератор
'$sim
$lib «mcsbyte.lbx» 'подключаем библиотеку функций

Config Pinc.0 = Input: Portc.0 = 1 'кнопка Выбор
Config Pinc.1 = Input: Portc.1 = 1 'кнопка Вверх
Config Pinc.2 = Input: Portc.2 = 1 'кнопка Вниз

Config Portd = Output: Config Portb = Output 'порты на выход к которым подключен индикатор

Load1 Alias Portb.3 'нагрузка №1
Load2 Alias Portb.4 'нагрузка №2
Vibor Alias Pinc.0 'кнопка Выбор
Up Alias Pinc.1 'кнопка Вверх
Down Alias Pinc.2 'кнопка Вниз

Config Adc = Single, Prescaler = Auto, Reference = Avcc 'настраиваем АЦП
Dim W As Byte, Y As Byte, X As Byte, I As Byte, Z As Byte, Chislo(6) As Integer, Channel_1 As Integer, On1 As Word, Off1 As Word, Channel_2 As Integer, On2 As Word, Off2 As Word, Channel_3 As Integer, Sostoyanie As Byte, Sostoyanie_2 As Byte, Copy_print As Integer, Copy_print_sec As Integer, Copy_var As Integer, _print As Integer, _print_sec As Integer, Pokazaniya As Integer, View_menu As Byte, Booton_flag As Bit, Booton_flag_2 As Bit, Count As Word, Error_flag As Bit
Dim On2_l As Word, On2_h As Word, Off2_l As Word, Off2_h As Word, Diapazon As Byte
Dim Razryad_1 As Byte, Razryad_2 As Byte, Temp_najatiya As Byte, Indicator As Byte
Dim Menu_punkt As Byte 'пункт меню
Dim Set_flag As Bit 'флаг режима настройки уставок
Dim Podskaz_flag As Bit, Podskaz_flag_sec As Bit 'флаги режима отображения подсказок на соответствующих каналах
Dim Default As Eram Byte At &H14 'переменная EEPROM для записи начальных уставок для первого включения прибора

'A Alias Portd.5: B Alias Portd.7: C Alias Portd.3: D Alias Portd.1 'порты, к которым подключены сегменты индикатора
'E Alias Portd.0: F Alias Portd.6: G Alias Portd.4: H Alias Portd.2 '
'Dig1 Alias Portb.0: Dig2 Alias Portb.1: Dig3 Alias Portb.2 ' порты, к которым подключены общие аноды 1го индикатора
'Dig1_sec Alias Portb.5: Dig2_sec Alias Portb.6: Dig3_sec Alias Portb.7 ' порты, к которым подключены общие аноды 2го индикатора

A Alias Portd.2: B Alias Portd.6: C Alias Portb.7: D Alias Portb.2 'порты, к которым подключены сегменты индикатора
E Alias Portd.0: F Alias Portb.6: G Alias Portd.7: H Alias Portb.5 '
Dig1 Alias Portd.3: Dig2 Alias Portd.4: Dig3 Alias Portd.1 ' порты, к которым подключены общие аноды 1го индикатора
Dig1_sec Alias Portd.5: Dig2_sec Alias Portb.0: Dig3_sec Alias Portb.1 ' порты, к которым подключены общие аноды 2го индикатора

For X = 1 To 6 ' присваиваем всем цифрам пустоту, чтоб в момент включения не высвечивались нули
Chislo(x) = 11
Next
'*********** ___ Переключение типа индикаторов (ОК/ОА) ___ *********************
Readeeprom Indicator, 30
If Vibor = 0 Then ' Удерживая кнопку УСТ, подаем питание.
For X = 1 To 200
If X = 150 Then ' При длительном нажатии
Set Booton_flag
Toggle Indicator.3 ' Переключаем младший (четвертый) бит переменной. Номер бита выбрал произвольно
Writeeeprom Indicator, 30
If Indicator = &B00000000 Then ' Если переключили на ОА, зажжем все сегменты идикатора для подтверждения
Reset A: Reset B: Reset C: Reset D: Reset E: Reset F: Reset G: Reset H
Set Dig1: Set Dig2: Set Dig3: Set Dig1_sec: Set Dig2_sec: Set Dig3_sec
End If
If Indicator = &B00001000 Then ' Если переключили на ОK, зажжем все сегменты идикатора для подтверждения
Set A: Set B: Set C: Set D: Set E: Set F: Set G: Set H
Reset Dig1: Reset Dig2: Reset Dig3: Reset Dig1_sec: Reset Dig2_sec: Reset Dig3_sec
End If
Waitms 1500
Exit For
End If
If Vibor = 1 Then Exit For
Waitms 10
Next
End If
'*******************************************************************************
If Default = 255 Then 'при первом запуске, когда Default=255
Off1 = 120 'присваиваем начальный уставки
On1 = 110
' Off2 = 60
' On2 = 50
On2_l = 800
On2_h = 99
Off2_l = 600
Off2_h = 99
Indicator = &B00000000 ' для ОА. Indicator = &B00001000 для ОК
Razryad_1 = &B10111011
Razryad_2 = &B11101110
Writeeeprom On1, 0 ' Записываем в EEPROM уставку ON1
Writeeeprom Off1, 5 ' Записываем в EEPROM уставку OFF1
' Writeeeprom On2, 10 ' Записываем в EEPROM уставку ON2
' Writeeeprom Off2, 14 ' Записываем в EEPROM уставку OFF2
Writeeeprom On2_l, 22
Writeeeprom On2_h, 24
Writeeeprom Off2_l, 26
Writeeeprom Off2_h, 28
Writeeeprom Razryad_1, 16
Writeeeprom Razryad_2, 17
Writeeeprom Indicator, 30
Default = 100 ' присваиваем значение 100(произвольное, отличное от 255) и больше этот кусок кода выполняться не будет
End If
Readeeprom On1, 0 ' Считываем из EEPROM уставку ON1
Readeeprom Off1, 5 ' Считываем из EEPROM уставку OFF1
' Readeeprom On2, 10 ' Считываем из EEPROM уставку ON2
' Readeeprom Off2, 14 ' Считываем из EEPROM уставку OFF2
Readeeprom On2_l, 22
Readeeprom On2_h, 24
Readeeprom Off2_l, 26
Readeeprom Off2_h, 28
Readeeprom Razryad_1, 16
Readeeprom Razryad_2, 17

Config Timer0 = Timer, Prescale = 8: On Timer0 Pulse ' конфигурируем таймер 0 и назначаем подпрограмму которая выполняется при переполнении таймера
Config Timer1 = Timer, Prescale = 1: On Timer1 Bootons ' конфигурируем таймер 1 и назначаем подпрограмму которая выполняется при переполнении таймера
Enable Interrupts: Enable Timer0: Enable Timer1 ' разрешаем прерывания, таймер 0, таймер 1
Start Timer0: Start Timer1
Start Adc ' начало преобразования

If On2_l > 999 Then On2 = On2_h Else On2 = On2_l
If Off2_l > 999 Then Off2 = Off2_h Else Off2 = Off2_l

Do
If X > 40 Then ' увеличили период опроса АЦП, чтобы значения не прыгали
Stop Timer0: Stop Timer1 ' на время преобразования останавливаем таймеры
Channel_1 = Getadc(5) ' Вольтметр 0..500 В 'опрос АЦП (диапазон от 0 до 1023) (1 канал)
' Channel_1 = 1023 — Channel_1 'инвертирование раскомментировать===========
Channel_1 = Channel_1 / 2.046 'пересчет тут любая формула для требуемого диапазона или необходимой характеристики
If Channel_1 > 500 Then Channel_1 = 500 'верхний предел показаний

Channel_2 = Getadc(4) ' Ток 1,00… 9,99 А 'опрос АЦП (2 канал)
' Channel_2 = Channel_2 'пересчет тут любая формула для требуемого диапазона или необходимой характеристики
If Channel_2 > 999 Then Channel_2 = 999 'верхний предел показаний
' Razryad_2 = &B01110111
Diapazon = 2
If Channel_2 < 100 Then
Channel_2 = Getadc(3) ' Ток 0… 999 mА 'опрос АЦП (3 канал)
If Channel_2 > 999 Then Channel_2 = 999
' Razryad_2 = &B11101110
Diapazon = 1
End If
X = 0 '
End If
Start Timer0: Start Timer1
If On1 < Off1 Then 'если уставка ON1 < OFF1 то режим нагревателя
If Channel_1 <= On1 Then Sostoyanie = 1 'если значение первого канала АЦП ниже уставки ON1, то включаем нагрузку №1
If Channel_1 >= Off1 Then Sostoyanie = 0 'если значение первого канала АЦП выше уставки OFF1, то выключаем нагрузку №1
Else 'если уставка ON1 > OFF1 то режим охладителя
If Channel_1 >= On1 Then Sostoyanie = 1 'если значение первого канала АЦП выше уставки ON1, то включаем нагрузку №1
If Channel_1 <= Off1 Then Sostoyanie = 0 'если значение первого канала АЦП ниже уставки OFF1, то выключаем нагрузку №1
End If

If Sostoyanie = 1 Then Set Load1 Else Reset Load1 'управление 1м каналом нагрузки
0
' If On2 < Off2 Then 'если уставка ON2 < OFF2 то режим нагревателя
' If Channel_2 <= On2 Then Sostoyanie_2 = 1 'если давление ниже уставки ON2, то включаем нагрузку №2
' If Channel_2 >= Off2 Then Sostoyanie_2 = 0 'если давление выше уставки OFF2, то выключаем нагрузку №2
' Else 'если уставка ON2 > OFF2 то режим охладителя
' If Channel_2 >= On2 Then Sostoyanie_2 = 1 'если давление выше уставки ON2, то включаем нагрузку №2
' If Channel_2 <= Off2 Then Sostoyanie_2 = 0 'если давление ниже уставки OFF2, то выключаем нагрузку №2
' End If
If Diapazon = 2 Then
If Channel_2 >= On2_h Then Sostoyanie_2 = 1
If Channel_2 < Off2_h Then Sostoyanie_2 = 0
Else
If Channel_2 >= On2_l Then Sostoyanie_2 = 1
If Channel_2 < Off2_l Then Sostoyanie_2 = 0
End If

If Sostoyanie_2 = 1 Then Set Load2 Else Reset Load2 'управление 2м каналом нагрузки

Loop

'*******************************************************************************
' Индикация

Pulse:
If View_menu <> 0 And Error_flag = 0 Then Incr Count 'если находимся в меню и нет ошибки ввода уставок, то инкриментируем счетчик, который отвечает за автоматический выход из меню (~ через 5 сек)
If Count > 7000 Then 'задается время автоматического выхода из меню
Count = 0 'сброс счетчика
View_menu = 0 ' выход из меню в основной режим
End If
' H = 1 ' выключаем точку на индикаторе
Stop Timer0 'останавливаем таймер 0
Select Case View_menu 'в зависимости от пункта меню, записываем в переменные расчета следующие данные
Case 0:
_print = Channel_1 ' основной режим. 1й индикатор показывает значение АЦП 1го канала
_print_sec = Channel_2 ' основной режим. 2й индикатор показывает значение АЦП 2го канала
If Diapazon = 2 Then Razryad_2 = &B01110111 Else Razryad_2 = &B11101110

Case 1:
_print = On1 ' 1й индикатор показывает значение уставки ON1
_print_sec = Channel_2 ' 2й индикатор показывает значение АЦП 2го канала
Case 2:
_print = Off1 ' 1й индикатор показывает значение уставки OFF1
_print_sec = Channel_2 ' 2й индикатор показывает значение АЦП 2го канала
Case 3:
_print = Channel_1 ' 1й индикатор показывает значение АЦП 1го канала
If On2_l = 1000 Then Razryad_2 = &B01110111 Else Razryad_2 = &B11101110
_print_sec = On2 ' 2й индикатор показывает значение уставки ON2
Case 4:
_print = Channel_1 ' 1й индикатор показывает значение АЦП 1го канала
If Off2_l = 1000 Then Razryad_2 = &B01110111 Else Razryad_2 = &B11101110
_print_sec = Off2 ' 2й индикатор показывает значение уставки OFF2

End Select

If Podskaz_flag = 0 Then 'если не выводим на 1й индикатор подсказки, то работаем с числами
Copy_print = _print '
Copy_var = Copy_print '
For I = 3 To 1 Step -1 ' цикл в котором разбивается переменная на 3 числа
Chislo(i) = Copy_print Mod 10 ' заносим в масив последнюю цифру от числа Copy_print(123 mod 10 = 3)
Copy_print = Copy_print / 10 ' отсекаем последнюю цифру от числа Copy_print (123/10=12)
Next ' убираем незначимые нули
If Copy_var < 100 Then Chislo(1) = 11 'для 2х значного числа
' If Copy_var < 10 Then Chislo(2) = 11 'для однозначного числа
End If
If Podskaz_flag_sec = 0 Then 'если не выводим на 2й индикатор подсказки, то работаем с числами
Copy_print_sec = _print_sec '
Copy_var = Copy_print_sec
For I = 6 To 4 Step -1 ' цикл в котором разбивается переменная на 3 числа
Chislo(i) = Copy_print_sec Mod 10 ' заносим в масив последнюю цифру от числа _print(123 mod 10 = 3)
Copy_print_sec = Copy_print_sec / 10 ' отсекаем последнюю цифру от числа Copy_print_sec (123/10=12)
Next 'убираем незначимые нули
' If Copy_var < 100 Then Chislo(4) = 11 'для 2х значного числа
' If Copy_var < 10 Then Chislo(5) = 11 'для однозначного числа
End If

If Indicator = 0 Then ' Гасим индикаторы перед выводом информации
Reset Dig1: Reset Dig2: Reset Dig3: Reset Dig1_sec: Reset Dig2_sec: Reset Dig3_sec ' Для индикатора с ОА
Else
Set Dig1: Set Dig2: Set Dig3: Set Dig1_sec: Set Dig2_sec: Set Dig3_sec 'Для индикатора с ОK
End If

Incr W: If W > 6 Then W = 1 ' выбираем какую цифру сейчас включать
Y = 0
Gosub Look: A = Z ' переходим к подпрограмме Look, которая определяет нужно ли сейчас загорется сегменту А
Gosub Look: B = Z
Gosub Look: C = Z
Gosub Look: D = Z
Gosub Look: E = Z
Gosub Look: F = Z
Gosub Look: G = Z

Select Case W ' включаем цифру(разряд) которую выбрали (w). Подаем плюс на общий провод конкретной цифры (разряда)
Case 1:
If Indicator = 0 Then
Set Dig3
If Podskaz_flag = 0 Then H = Razryad_1.3 Else H = 1
Else
Reset Dig3
If Podskaz_flag = 0 Then
H = Razryad_1.3
Toggle H
Else
H = 0
End If
End If
Case 2:
If Indicator = 0 Then
Set Dig2
If Podskaz_flag = 0 Then H = Razryad_1.2 Else H = 1
Else
Reset Dig2
If Podskaz_flag = 0 Then
H = Razryad_1.2
Toggle H
Else
H = 0
End If
End If
Case 3:
If Indicator = 0 Then
Set Dig1
If Podskaz_flag = 0 Then H = Razryad_1.1 Else H = 1
Else
Reset Dig1
If Podskaz_flag = 0 Then
H = Razryad_1.1
Toggle H
Else
H = 0
End If
End If
Case 4:
If Indicator = 0 Then
Set Dig3_sec
If Podskaz_flag_sec = 0 Then H = Razryad_2.3 Else H = 1
Else
Reset Dig3_sec
If Podskaz_flag = 0 Then
H = Razryad_2.3
Toggle H
Else
H = 0
End If
End If
Case 5:
If Indicator = 0 Then
Set Dig2_sec
If Podskaz_flag_sec = 0 Then H = Razryad_2.2 Else H = 1
Else
Reset Dig2_sec
If Podskaz_flag = 0 Then
H = Razryad_2.2
Toggle H
Else
H = 0
End If
End If
Case 6:
If Indicator = 0 Then
Set Dig1_sec
If Podskaz_flag_sec = 0 Then H = Razryad_2.1 Else H = 1
Else
Reset Dig1_sec
If Podskaz_flag = 0 Then
H = Razryad_2.1
Toggle H
Else
H = 0
End If
End If
End Select
'

Start Timer0
Return
'
Look: ' подпрограмма которая определяет нужно ли сейчас гореть сегменту, который вызвал эту подпрограмму
Z = Chislo(w) * 7: Z = Y + Z ' определяем порядковый номер числа из таблици DATA. W — это цифра которую будем выводить 1..2..3, Y это номер сегмента (A=0 B=1 C=2...G=7)
If Indicator = 0 Then
Z = Lookup(z, Cifri_oa) ' выбираем из таблици включить или выключить нужный сегмент -OA
Else
Z = Lookup(z, Cifri_ok) '-OK
End If
Incr Y 'Y это номер сегмента (A=0 B=1 C=2...). Chislo(w) * 7 — переход на начало нужной строки Data.Z = Y + Z — по очереди перебираем сегменты в строке.
Return
0
Для вставки кода есть тег <code>.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.