В розетке 220 Вольт — это RMS, а амплитудное там 310 Вольт.
По пальцам бьют 310В, но воду кипятят 220В
По пальцам бьют 310В, но воду кипятят 220В
После публикации моей статьи «RMS-вольтметр" было много вопросов, на которые у меня не было ответа: отсутствовал исходник программы микроконтроллера.
Исходник для меня тогда тоже остался тайной, т. к. прошивка бралась готовая, чужая.
Сейчас я попытаюсь исправиться, правда одно, но — микроконтроллер я применил из серии PIC18. Программа прошла проверку на PIC18F2520. Теперь выясним, что такое среднеквадратичное (действительное) напряжение.
Вот цитата из статьи А. Долгий в журнале Радио № 6 за 2008 г. 23 стр.:
Согласно закону Джоуля, количество тепла, выделяющегося на нагрузке с активным сопротивлением, пропорционально квадрату приложенного к нему напряжения. Чтобы измерить эффективное значение переменного напряжения (тока), необходимо в течении некоторого времени возводить в квадрат его мгновенные значения, усреднить результаты и извлечь из среднего квадратный корень.
Поэтому эффективное значение часто называют среднеквадратичным. Принятая в английском языке аббревиатура RMS (Root Mean Square — «корень из среднего квадрата») описывает, по существу, процедуру его вычисления.
![RMS-вольтметр на PIC18F2520. Немного теории и исходники]()
Поэтому эффективное значение часто называют среднеквадратичным. Принятая в английском языке аббревиатура RMS (Root Mean Square — «корень из среднего квадрата») описывает, по существу, процедуру его вычисления.

Практически, нам известна частота измеряемого напряжения — 50 Гц.
Даже, если мы будем использовать трехфазный выпрямитель (50 Гц х 6 = 300 Гц), выходная частота будет кратна 50 Гц.
Для чего нам это нужно? Хотелось бы сделать некоторое количество измерений при одинаковых начальных и конечных положений полуволн. Полный период при 50 Гц занимает время 1 / 50Гц = 20 мс.
Сколько измерений мы можем сделать? Оно, конечно, чем больше, тем лучше. Микроконтроллер любит работать с бинарными числами, значит, мы можем выбрать количество из следующего ряда: 2, 4, 8, 16, 32, 64, 128, 256, 512.… Судя по формуле «n» является не только количеством измерений, но и делителем. Если выбрать 2, то не стоило бы и возиться. Если 512 — тогда надо использовать при делении 2 регистра — сложновато.
Попробуем 256. Тогда операцию деления мы полностью исключим из программы, используя простые сдвиги. Хотя, в дальнейшем, окажется, что и операция сдвига нам не понадобится.
Самая сложная операция — вычисление корня. В школе меня учили находить корень вычислением столбиком. Вырезка, где довольно подробно описывается эта метода, находится в архиве. В архиве есть и проект для Протеуса.
Я не стал пытаться все описывать подробно — многовато.
Удачи!
LIST P=18F2520
#INCLUDE <P18F2520.INC>
;*********************************************************************************************
; Конфигурирование микроконтроллера
;*********************************************************************************************
__CONFIG 0×300000, b'00100100'; CONFIG1L
__CONFIG 0×300001, b'00000010'; CONFIG1H
__CONFIG 0×300002, b'00000000'; CONFIG2L
__CONFIG 0×300003, b'00010101'; CONFIG2H
; Не используется. ; CONFIG3L
__CONFIG 0×300005, b'00000000'; CONFIG3H
__CONFIG 0×300006, b'10000001'; CONFIG4L
; Не используется. ; CONFIG4H
__CONFIG 0×300008, b'00001111'; CONFIG5L
__CONFIG 0×300009, b'11000000'; CONFIG5H
__CONFIG 0×30000A, b'00001111'; CONFIG6L
__CONFIG 0×30000B, b'11100000'; CONFIG6H
__CONFIG 0×30000 °C, b'00001111'; CONFIG7L
__CONFIG 0×30000D, b'01000000'; CONFIG7H
;*********************************************************************************************
cblock h'100' ; Программа достаточно короткая,
; все умещается в одном банке памяти.
; используем 1 банк.
W_temp ; Сохранение контекста
ST_temp ; при прерывании
ARG_L ; Младший бит текущего измерения
ARG_H ; Старший бит текущего измерения
RES0 ; Результат текущего измерения,
RES1 ; возведенного в квадрат, т. е.
RES2 ; (ARG_L:ARG_H)*(ARG_L:ARG_H)=
RES3 ; = RES0:RES1:RES2:RES3
Byte_0 ; Здесь хранится накапливаемая
Byte_1 ; сумма RES0:RES1:RES2:RES3
Byte_2 ;
Byte_3 ;
Temp ; Временные файлы
Temp_0 ; на все (ну, почти все)
Temp_1 ; случаи жизни
Result_L ; Результат кропотливой работы:
Result_H ; измеряли, умножали, суммировали,
; и так 256 раз. Наконец, нашли корень,
; и уложили в эти регистры.
AL ; Регистры, где хранятся данные в 16-ричном виде,
AH ; выводимые на дисплей.
FigSeg ; Знакоместо цифры на дисплее.
A100 ; Количество сотен выводимого результата на дисплей,
A10 ; Количество десяток выводимого результата на дисплей,
A1 ; Количество единиц выводимого результата на дисплей.
State ; Статус программы.
Count ; Счетчик измерений.
Q_Count ; Счетчик парных бит.
endc
; Назначение бит регистра «State»
new equ 0 ; 1 = Есть новые данные,
stop equ 1 ; 1 = Сделано 256 измерений.
;*********************************************************************************************
;* Начало кода программы
;*********************************************************************************************
Org 0×00
bra Start
;*********************************************************************************************
;* Обслуживание прерываний
;*********************************************************************************************
org 0×08 ; Вектор прерываний
INT
movwf W_temp; Говорят, что PIC18 сам сохраняет
swapf STATUS,w; контекст, но, — «береженого Бог бережет»
movwf ST_temp; Почему «swapf»? — эта команда не изменяет STATUS,
; а то обычным «mov» можно и попортить.
;*********************************************************************************************
clrwdt ; Чтобы «собачка» не кусалась.
btfss PIR1, TMR1IF; Прерывание по переполнению TMR1?
bra ACP ; Прерывание по АЦП?
bcf PIR1, TMR1IF; разрешим новое прерывание.
nop
movlw .254 ; Мы должны сделать все 256 измерений за один период сети
movwf TMR1H ; 20мс / 256 = 78,125мкс
movlw .136 ; для получения прерывания через 78мкс
movwf TMR1L ; пред устанавливаем счетчик таймера.
bsf ADCON0, GO_DONE; Включаем АЦП
bra End_INT; и ждем прерывания от него.
ACP
btfss PIR1, ADIF; Прерывание по АЦП ?
bra qqw ; Какое-то непонятное прерывание, его не дожно быть.
bcf PIR1, ADIF; Подготовили АЦП для следующего раза.
dcfsnz Count ; Если уже сделали 256 измерений. то
bsf State, stop; установим флаг окончания.
bsf State, new; Признак получения новых данных.
;*********************************************************************************************
; Динамическая индикация
;*********************************************************************************************
clrf WREG ; Гасим индикаторы по двум причинам:
iorlw b'11111000'; Чтобы не зажглись дополнительно еще другие индикаторы, —
movwf PORTC ; выхода контроллера не «железные».
; Чтобы в Протеусе полюбоваться динамической индикацией, а
; не хаотическим мельтешением индикаторов. Еще можно в
; свойствах индикатора в Протеуса уменьшить время.
rlncf POSTINC0, WREG; Красивая команда — и сдвинулись к следующему регистру,
; и прочитали его содержимое (A100, A10, A1).
call Segment; Преобразовали в 7-сегментный код для индикатора,
movwf PORTB ; Включили одну сторону индикатора.
movff FigSeg, WREG; Выбираем знакоместо
iorlw b'11111000'; масочка,
movwf PORTC ; Вот теперь индикатор зажегся.
rrncf FigSeg ; Готовимся к следующему индикатору
btfss FigSeg, 7; если, конечно,
bra End_INT; такое знакоместо есть.
lfsr 0, A100 ; Все индикаторы прошли, надо начинать новый круг:
movlw b'00000100'; начнем с индикатора А100.
movwf FigSeg ;
qqw ; Непонятное прерывание, мы не должны сюда приходить.
End_INT
swapf ST_temp,w; Надо снова перевернуть байт на место
movwf STATUS; и восстановить.
swapf W_temp,f; Так хитро восстановим и
swapf W_temp,w; аккумулятор, не затрагивая STATUS.
retfie ; Выходим из обработчика прерываний.
;*********************************************************************************************
; Перекодировка двоичных чисел для 7.сегметного индикатора.
;*********************************************************************************************
Segment
addwf PCL, f
; EDHCGAFB
retlw b'00101000' ; 0
retlw b'11101110' ; 1
retlw b'00110010' ; 2
retlw b'10100010' ; 3
retlw b'11100100' ; 4
retlw b'10100001' ; 5
retlw b'00100001' ; 6
retlw b'11101010' ; 7
retlw b'00100000' ; 8
retlw b'10100000' ; 9
;*********************************************************************************************
;*********************************************************************************************
;Инициализация
;*********************************************************************************************
Start
movlb 1 ; Включаем 1 банк памяти
clrwdt ; сбросим сторожевого пса
clrf A1 ; Очистим
clrf A10 ; показания
clrf A100 ; на индикаторах.
lfsr 0, A100 ; Начнем обслуживать индикаторы с А100,
movlw b'00000100'; а здесь указано его
movwf FigSeg ; знакоместо.
movlw b'00000001'; Устанавливаем требуемые
movwf TRISA ; порты на вход и на выход.
movlw b'00000000'; Устанавливаем требуемые
movwf TRISB ; порты на вход и на выход.
movlw b'10000000'; Устанавливаем требуемые
movwf TRISC ; порты на вход и на выход.
movlw b'00000111'; Отключим
movwf CMCON; компараторы
clrf ADCON0; используется
movlw b'00001001'; правое выравнивание
movwf ADCON1; делитель — Fosc/16, выбираем
movlw b'10001110'; на мультиплексор
movwf ADCON2; канал AN0, и включаем АЦП
bcf PIR1, ADIF
bsf PIE1, ADIE ; Прерывание от АЦП
bsf T1CON, TMR1ON
bsf PIE1, TMR1IE; Прерывание от таймера 1,
bsf INTCON, PEIE ; общее от периферии,
bsf INTCON, GIE ; и глобально.
;*********************************************************************************************
MainLoop
movlw b'00000001';
movwf ADCON0; Включаем АЦП, вход AN0
call Get_AD_Value ; Измеряем, квадратируем, суммируем, ищем корень…
movff Result_L, AL; Сохраняем результат измерений.
movff Result_H, AH;
bcf STATUS, C; Избавимся от ложного переноса
rrcf AH, f ; и сдвигом вправо
rrcf AL, f ; разделим на два.
call BIN2DEC3; Преобразуем бинарный ответ в привычный рам — десятичный.
bra MainLoop; Зацикливаемся, начинаем все измерения сначала.
;*********************************************************************************************
; Подпрограммы
;*********************************************************************************************
; Выполнение АЦП с выбранного ранее входа
; с вычислением среднеквадратичного значения из 256 измерений.
;*********************************************************************************************
Get_AD_Value
clrf Byte_0 ; Подготавливаемся:
clrf Byte_1 ; очистим рабочие
clrf Byte_2 ; регистры
clrf Byte_3 ; от
clrf Count ; возможного
clrf State ; «мусора».
Get_AD_Value_1
btfss State, new ; Ждем
bra Get_AD_Value_1; новые данные.
bcf State, new ; Есть новые данные, сбросим флажок:
movff ADRESH, ARG_H; Пере сохраним результат
movff ADRESL, ARG_L; измерения в ARG (L,H).
movff ARG_H, Temp_1; Пере сохраним результат
movff ARG_L, Temp_0; измерения в Temp (L,H).
Get_AD_Value_2
call SQUARING ; Возведем в квадрат,
call SUMMA ; суммируем.
btfss State, stop ; Все 256 измерений?
bra Get_AD_Value_1; Еще нет, продолжаем измерения.
call SQRT24 ; Все 256 измерений выполнены, и мы должны
; сумму разделить на количество измерений (256),
; но если откинуть младший байт суммы (Byte_0), это
; равносильно делению на 256 (какое халявное
; деление — приятно, однако)
Return
SQUARING
;*********************************************************************************************
; Результат измерения переносим в регистр ARG,
; возводим в квадрат и сохраняем в регистре RES.
;*********************************************************************************************
movff ARG_L, WREG ; ARG_L * ARG_L
mulwf ARG_L ;
movff PRODH, RES1 ;сохраняем в
movff PRODL, RES0 ;PRODL : PRODH
movff ARG_H, WREG; ARG_H * ARG_H
mulwf ARG_H ;
movff PRODH, RES3 ;сохраняем в
movff PRODL, RES2 ;PRODL : PRODH
movff ARG_L, WREG; ARG_L * ARG_H
mulwf ARG_H ;
movff PRODL, WREG; PRODL + RES1
addwf RES1, f ;
movff PRODH, WREG; PRODH + RES2
addwfc RES2, f ;
clrf WREG ; RES3 + C (перенос)
addwfc RES3, f ;
movff PRODL, WREG; PRODL + RES1
addwf RES1, f ;
movff PRODH, WREG; PRODH + RES2
addwfc RES2, f ;
clrf WREG ; RES3 + C (перенос)
addwfc RES3, f ;
return ;
;*********************************************************************************************
SUMMA
;*********************************************************************************************
; Сложение RES с Byte,
; сумму сохраняем в регистре Byte.
;*********************************************************************************************
movff RES0, WREG ; RES0 + Byte_0 сохраняем в
addwf Byte_0, f ; Byte_0
movff RES1, WREG ; C + RES1 + Byte_1 сохраняем в
addwfc Byte_1 ; Byte_1
movff RES2, WREG ; C + RES2 + Byte_2 сохраняем в
addwfc Byte_2, f ; Byte_2
movff RES3, WREG ; C + RES3 + Byte_3 сохраняем в
addwfc Byte_3 ; Byte_3
return
;*********************************************************************************************
;Преобразование двоичного слова в десятичное число
;*********************************************************************************************
BIN2DEC3 ; Обнулим все регистры, которые содержат количество:
clrf A1 ; единичек,
clrf A10 ; десятков и
clrf A100 ; сотен в регистрах AH:AL.
;*********************************************************************************************
; Например в AH:AL содержится число «02:17»,
; это будет «535» в привычном десятичном исчислении.
; Тогда операцию 535-100 не переходя в отрицательные числа
; мы сможем проделать только 5 раз.
; При каждом положительном ответе прибавляем 1 в А100
; Остаток равен 535-100-100-100-100-100 = 35
; а в регистре А100 лежит удачное количество отниманий — 5
;*********************************************************************************************
CV100
incf A100,f ; Заранее прибавили единичку в счетчик сотен.
movlw .100 ; т.е AH:AL — 00:64 (.100 — в десятичном виде),
subwf AL, f ; AL — .100 , сохраняем ответ в AL
clrf WREG ; старший байт числа 100 равен «0»,
subwfb AH, f ; но ведь мог быть заем, вот и отнимем «0» с учетом заема
bc CV100 ; Прежний остаток был больше 100, попробуем еще отнять.
movlw .100 ; Увы, перебор, надо вернуть обратно последнюю 100
addwf AL, f ; AH:AL + .100
clrf WREG ; с учетом
addwfc AH, f ; переноса.
decf A100,f ; Последний раз мы прибавили единичку в A100 зря, надо исправить.
;*********************************************************************************************
; 535 — 100 — 100 — 100 — 100 — 100 = 35
; а в регистре А100 лежит удачное количество отниманий — 5.
; Теперь посчитаем количество десяток в остатке.
; Операцию 35 — 10 повторяем столько раз, пока не перескочили в отрицательные числа
; 35 — 10 — 10 — 10 = 5
; в регистре A10 лежит удачное количество отниманий — 3
;*********************************************************************************************
CV10
incf A10,f ; Заранее прибавили единичку в счетчик десяток.
movlw .10 ; т.е AH:AL — 00:0A (.10 — в десятичном виде),
subwf AL, f ; AL — .10 , сохраняем ответ в AL
clrf WREG ; старший байт числа 10 равен «0»,
subwfb AH, f ; но ведь мог быть заем, вот и отнимем «0» с учетом заема
bc CV10 ; Прежний остаток был больше 10, попробуем еще отнять.
movlw .10 ; Увы, перебор, надо вернуть обратно последнюю 10
addwf AL, f ; AH:AL + .10
clrf WREG ; с учетом
addwfc AH, f ; переноса.
decf A10,f ; Последний раз мы прибавили единичку в A100 зря, надо
; исправить.
;*********************************************************************************************
; 35 — 10 — 10 — 10 = 5
; в регистре A10 лежит удачное количество отниманий — 3
; В остатке остались только единички
; значит, просто перенесем содержимое остатка в счетчик единиц — A1
; A1 = 5
;*********************************************************************************************
CV1
movff AL,A1;
return
;*********************************************************************************************
SQRT24
;*********************************************************************************************
; Вычисляем корень из 24 разрядного числа,
; уложенного в регистры — Byte_1, Byte_2, Byte_3,
; где результатом являются регистры Result_L, Result_H;
; маской регистры Temp_0, Temp_1;
; Алгоритм нахождения корня называется «столбиком».
;*********************************************************************************************
clrf Result_H ; Подготовимся к вычислениям, предварительно
clrf Result_L ; очистив используемые регистры.
clrf Temp_0 ;
clrf Temp_1 ;
movlw .12 ; Количество парных бит
movwf Q_Count ; 3 слова по 8 бит = 24 бита, 24/2 = 12 пар.
Shift
rlcf Byte_1, f ; Приписываем к текущему остатку
rlcf Byte_2, f ; очередную пару бит.
rlcf Byte_3, f ; Остаток = 4 * Остаток +2 бита из подкоренного
rlcf Temp_0, f ; (Temp_0,1)= 4 * (Temp_0,1) + 2 бита из (Byte_n)
rlcf Temp_1, f ;
rlcf Byte_1, f ; Умножение проводим
rlcf Byte_2, f ; двойным сдвигом
rlcf Byte_3, f ; «влево с учетом переноса»
rlcf Temp_0, f ;
rlcf Temp_1, f ;
rlcf Result_L, f ; Находим очередную двоичную
rlcf Result_H, f ; цифру результата:
rlcf Result_L, f ;
rlcf Result_H, f ; 4*Result_+1
bcf Result_L, 1 ;
bsf Result_L, 0 ; это здесь мы прибавили единичку.
SubTest
movff Result_L, WREG;
subwf Temp_0,f ; Temp_0,1 < Result_ ?
movff Result_H, WREG;
subwfb Temp_1,f ;
bc Set1 ; Если Результат оказался меньше
; Остатка, то ставим «1» в текущем бите ответа.
movff Result_L, WREG; Корректируем «текущий остаток»:
addwf Temp_0,f ; надо все вернуть,
movff Result_H, WREG; ведь мы отняли от остатка
addwfc Temp_1,f ; слишком много, в ответе
bra Set0 ; текущий бит надо установить в «0».
Set1
bsf Result_L, 1 ; Текущий бит ответа установим в «1».
Set0
rrcf Result_H, f ; Корректируем «текущий результат»:
rrcf Result_L, f ; Result_0,1 = 2 * Result_0,1
bcf Result_H, 7 ;
decfsz Q_Count,f ; Подсчитали обработанную пару,
bra Shift ; еще не все пары обработали, продолжим.
;*********************************************************************************************
; Округление результата.
; Алгоритм основан на сравнивании остатка (находящего в регистрах Temp_0:Temp_1),
; с результатом, (регистры Result_L:Result_H).
; Если остаток больше результата, то округление идет в большую сторону — прибавим единичку.
; Если остаток меньше результата, то округление идет в меньшую сторону — результат не меняется.
; Если остаток равен нулю, то мы получили точный целочисленный результат.
;*********************************************************************************************
movff Result_L, WREG; Что больше, остаток или ответ?
subwf Temp_0,f ; Проверим операцией отнимания,
movff Result_H, WREG; учитывая перенос.
subwfb Temp_1,f ;
bnc End_SQRT24 ; Если Результат больше остатка, округляем в меньшую
; сторону.
incf Result_L, f ; Надо округлять в большую сторону:
clrf WREG ; добавим единичку
addwfc Result_H, f ; к ответу.
End_SQRT24
return ; УРА!!! Корень нашелся.
;*********************************************************************************************
end ; Отмучились.
Файлы
🎁Исходники и проект для Протеуса 645.2 Kb ⇣ 146Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.