Наш магазин на eBay Наш канал в telegram

Параллельный программатор / фьюз-бит доктор для контроллеров avr

Введение

Получив из Китая очередную партию чипов ATtiny2313 для изготовления шлюзов UART-to-I2C/SPI/1W я, вдруг, обнаружил, что эти чипы совершенно невозможно прошить последовательным внутрисхемным (ICSP) программатором. Чипы при этом были в заводской упаковке и без следов пайки. Ранее я уже сталкивался с ситуациями, когда китайцы продавали новые рабочие чипы, но заранее прошитые под какие-либо устройства (например, — вот). Такие чипы прошивают нужной прошивкой прямо на заводе, но, видимо, время от времени случаются накладки, заказчик не забирает партию и чипы попадают в открытую продажу.

Ситуация в общем-то обычная, сложность же конкретно с контроллерами AVR заключается в том, что при сбросе определённых фьюзов, прошить контроллер становится возможно только параллельным программатором в режиме высоковольтного программирования (с использованием напряжения +12 Вольт). Схемы фьюз-докторов для AVR можно найти в интернете, однако минус всех этих схем заключается в полном отсутствии описания их работы. Выложена просто готовая прошивка, которую нужно залить в управляющий схемой контроллер, включить, нажать кнопку и будет вам счастье. Ни исходников, ни подробностей, вообще никаких объяснений.

Для меня, как для радиохламера, такой подход совершенно невозможен и невыносим, поэтому решено было разработать свой собственный программатор / фьюз-бит доктор для микроконтроллеров AVR, описать как он работает и выложить в открытый доступ не только прошивку, но и исходники программы для микроконтроллера с комментариями. Итак, поехали:

В любом даташите на микроконтроллеры AVR есть глава «Memory Programming». Если микроконтроллер поддерживает параллельное программирование (здесь и далее в этой статье под этим термином понимается заливка прошивки в микроконтроллер), то в этой главе есть раздел «Parallel Programming». В этом разделе, во-первых, описано какие сигналы и ноги используются при параллельном программировании, во-вторых, описаны используемые при этом команды и алгоритмы и, в третьих, приводятся диаграммы сигналов. В общем, — всё, что нужно для успеха операции.

Для начала разберёмся с сигналами. Сигналы используются следующие:

  • 8 сигналов для параллельного приёма/передачи байта данных DATA[7:0]. Сигналы передаются в обоих направлениях в зависимости от сигнала OE.
  • 9 служебных сигналов:
    • RDY/BSY — уровень на этой линии сигнализирует о готовности контроллера принимать новые команды. Данные передаются от контроллера к программатору
    • OE — служит для переключения линий данных на вход (высокий уровень) или на выход (низкий уровень)
    • WR — используется для старта выполнения каких-либо действий (стирание / запись …)
    • BS2, BS1 — эти две линии используются для выбора старших или младших байт
    • XA1, XA0 — по уровням на этих двух линиях определяется какое действие будет выполняться по наличию тактового импульса
    • PAGEL — используется при программировании Flash/EEPROM
    • XTAL1 — используется для подачи тактовых импульсов

Кроме этого нам потребуется напряжение +12 Вольт (для перехода в режим программирования), +5 Вольт — для питания контроллера, ну и естественно нужно будет подключить нулевой провод, относительно которого задаются +12 и +5 Вольт, к ноге GND.

В некоторых контроллерах какие-то сигналы могут быть объединены (например, в ATtiny2313 объединены ноги BS1 и PAGEL, а также XA1 и BS2), кроме того, могут отличаться номера ног, на которые заводятся эти сигналы (например, в ATmega8 для данных используются два младших бита порта C и 5 младших бит порта B, а в ATtiny2313 для данных используется только порт B целиком).

С алгоритмами тоже всё оказалось довольно просто, — вся задача сводится к выставлению нужных уровней на некоторые служебные линии (XA1, XA0, BS2, BS1, OE) и линии данных, а также к генерированию положительных или отрицательных импульсов на других служебных линиях (XTAL1, WR, PAGEL). Плюс нужно управлять питанием +5 Вольт (+1 линия) и напряжением +12 Вольт (ещё +1 линия). Управление всеми нужными линиями решено было сделать командами через UART (для этого нужно ещё 2 линии).

Исходя из всего выше описанного, для реализаци устройства нужно иметь 21 линию I/O (8 — данные + 9 — служебные сигналы + 2 — управление линиями +5 и +12 Вольт + 2 — uart), что, согласитесь, довольно много. Чтобы ног на всё хватило, в качестве управляющего контроллера была выбрана ATmega8 (для прототипа, естественно, в dip-корпусе), — у неё 23 линии I/O.

Аппаратная часть

Схема:

схема параллельного программатора для avr

Справа на схеме нарисовано как подключать к ней микроконтроллеры ATtiny2313.

Печатная плата (DipTrace 3.2)

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

  • разводка выполнена для одностороннего текстолита (не нужно совмещать разные стороны при переносе рисунка на плату)
  • использован контроллер в dip-корпусе (возможность установки в кроватку + большие расстояния между ножками обеспечивают лёгкость пайки)
  • все нужные контакты выведены на линейки с большим шагом (2,54 мм)
  • есть разъём для внутрисхемного программирования управляющего контроллера (для отладки очень удобно прошивать контроллер прямо на плате, не вынимая из кроватки)

При разводке платы я также предусмотрел установку конденсаторов по линиям питания +5 и +12 Вольт (прямо рядом с разъёмом питания). Эти конденсаторы на схеме не нарисованы, но я решил их на всякий случай поставить. Большого тока схема не потребляет, поэтому будет достаточно конденсаторов на 10-47 мкФ, рассчитанных на максимальное напряжение 16-25 Вольт и выше.

После сборки устройство выглядит вот так:

внешний вид параллельного программатора для avr

Программная часть

В соответствии с приведёнными в даташитах алгоритмами и диаграммами наш программатор должен уметь делать следующие вещи:

  • переходить в режим программирования
  • устанавливать и считывать уровни на линиях DATA[7:0]
  • выставлять правильные уровни на линиях XA1, XA0, BS2, BS1, OE
  • генерировать импульсы заданной ширины на линиях WR, XTAL1, PAG

Учитывая, что переходить в режим программирования нужно из какого-то начального состояния, а также то, что линия PAG может быть совмещена с BS1, — для реализации заявленного выше функционала нам хватит всего 11-дцати UART-команд, которые я просто пронумеровал от 0x1 до 0xB:

  • 0x01 — установить все используемые для программирования выводы (включая выводы, управляющие линиями +5 и +12 вольт) в начальное состояние
  • 0x02 — выполнить алгоритм перехода в режим программирования
  • 0x03 — выполнить алгоритм выхода из режима программирования
  • 0x04 — установить нужное состояние на линиях XA1/XA0 и BS2/BS1
  • 0x05 — установить нужное состояние на линии OE
  • 0x06 — сформировать положительный импульс на линии BS1
  • 0x07 — сформировать положительный импульс на линии PAG
  • 0x08 — сформировать положительный импульс на линии XTAL1
  • 0x09 — сформировать отрицательный импульс на линии WR
  • 0x0A — установить нужные уровни на линиях Data[7:0]
  • 0x0B — прочитать состояния линий Data[7:0]

Теперь некоторые моменты обсудим более детально:

Во-первых. В даташитах написано, что перед переходом в режим программирования нужно установить сигналы XA1, XA0, BS1 и WR (они называются Prog_enable) в ‘0000’. Поскольку сигнал на линии WR инвертирован (active low) и далее на всех диаграммах он начинается с высокого уровня, то логично было бы предположить, что его начальное состояние — это высокий уровень (именно он для него означает 0). На деле оказалось, что это работает только после подачи питания, а до подачи питания для всех ног 0 — это низкий уровень, и перед переходом в режим программирования все ноги, входящие в группу Prog_enable должны быть притянуты к низкому уровню.

Сам алгоритм перехода в режим программирования банален, — после того, как все линии Prog_enable притянуты к низкому уровню — нужно включить питание +5 Вольт и далее через 20-60 мкс включить напряжение +12 Вольт. Через 10 мкс после этого можно начинать переключать линии Prog_enable, а через 300 мкс — загружать каманды.

Выйти из режима программирования можно отключив от программируемого чипа +12 Вольт или +5 Вольт (или оба сразу).

Далее. Как можно заметить, для выполнения некоторых команд из приведённого выше списка нужны дополнительные данные. Например, команда «установить нужное состояние на линии OE» не позволяет сделать никаких выводов о том, что это за нужное состояние. Для таких команд мы будем устанавливать специальные флаги, которые будут означать, что следующий принимаемый по UART байт — это не новая команда, а те самые данные о «нужных состояниях» линий (состояния линий будут соответствовать определённым битам полученного байта).

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

Исходя из всего вышеописанного получилась следующая программа:

Программа для микроконтроллера, ассемблер

;-- radiohlam.ru --
.device   ATmega8
.include  "m8def.inc"
.list
;-- по-умолчанию фьюзы CSEL = 0001 (1 МГц), нужно поставить CSEL = 0100 (8 МГц)
;-- определяем свои переменные (и раздаём под них регистры)
.def	w=r16          ; аккумулятор (обменник)
.def	wait_flags=r17 ; флаги ожидания данных (0 - XA1/XA0/BS2/BS1, 1 - OE, 2 - Data)
.def	fd_reg=r19     ; счётчик для организации быстрых (маленьких) задержек (шаг 1 мкс)
.def	ld_reg=r20     ; счётчик для организации медленных (больших) задержек (шаг 200 мкс)
.def	w2=r21         ; ещё одна переменная для временного хранения всякой фигни
;-- определяем названия выводов и портов
.equ	Data_Out = 0x18 ; порт B - вывод данных (линии данных на выход)
.equ	Data_In  = 0x16 ; порт B - ввод данных (линии данных на вход)
 
.equ	RDY = 0         ; PC0
.equ	BS1 = 1         ; PC1
.equ	BS2 = 2         ; PC2
.equ	XA0 = 3         ; PC3
.equ	XA1 = 4         ; PC4
.equ	PAG = 5         ; PC5
 
.equ	OE      = 2     ; PD2
.equ	XTAL1   = 3     ; PD3
.equ	WR      = 4     ; PD4
.equ	Ctrl_12 = 6     ; PD6
.equ	Ctrl_5  = 7     ; PD7
 
.equ	RxD = 0         ; PD0
.equ	TxD = 1         ; PD1
 
.equ MinCommandNumber = 0x1 ; минимальный номер команды
.equ MaxCommandNumber = 0xB ; максимальный номер команды
;------------------------------------------
;-- начало программного кода
	.cseg
	.org 0
	rjmp	Init   ; переход на начало программы (вектор сброса)
;-- дальше идут вектора прерываний
;-- если не используем - reti, иначе - переход на начало обработчика
	reti           ; внешнее прерывание INT0
	reti           ; внешнее прерывание INT1
	reti           ; Timer2 COMP
	reti           ; Timer2 OVF
	reti           ; Timer1 CAPT
	reti           ; Timer1 COMPA
	reti           ; Timer1 COMPB
	reti           ; Timer1 OVF
	reti           ; Timer0 OVF
	reti           ; SPI, STC
	rjmp	RX_INT ; USART, Rx complete
	reti           ; USART, DataRegister Empty
	reti           ; USART, Tx complete
	reti           ; ADC conversion complete
	reti           ; EEPROM ready
	reti           ; Analog comparator
	reti           ; TWI (two-wire serial interface)
	reti           ; Store Program Memory Ready
;-- начало программы
Init:	;-- устанавливаем указатель вершины стека на старший байт RAM
	ldi w,Low(RAMEND)
	out SPL,w            ; младший байт адреса
	ldi w,High(RAMEND)
	out SPH,w            ; старший байт адреса
	;-- отключаем лишнее
	sbi ACSR,ACD         ; выключаем компаратор
	;-- инициализируем порты
	;-- DDR - направление работы (0 - вход, 1 - выход)
	;-- PORT - состояние защёлок / подтяжек (0 - низкий уровень / нет подтяжки, 1 - высокий уровень / есть подтяжка)
	;-- настраиваем PORTD (состояние - все, кроме PD2(OE), PD4(WR), PD7(+5) - нули, направление - все на выход)
	ldi w,0b10000000
	out PORTD,w
	ldi w,0b11111111
	out DDRD,w
	;-- настраиваем PORTC (состояние - все нули, направление - все на выход, кроме PC0(RDY))
	clr w
	out PORTC,w
	ldi w, 0b11111110
	out DDRC,w
	;-- настраиваем PORTB (состояние - все нули, направление - все на выход)
	clr w
	out PORTB,w
	ser w
	out DDRB,w
	;-- инициализируем UART
	clr w
	out UBRRH,w                    ; UBRR (для кварца 8 МГц и скорости 38400)
	ldi w,12                       ; равен 12, т.е. UBRRH = 0, UBRRL = 12
	out UBRRL,w
	ldi w,0b10001110               ; поднимаем биты USBS, UCSZ1:0 + старший бит (URSEL) должен быть 1 при записи в UCSRC
                                       ; в примере из оф.доки на мегу - косяк, они просто скопировали пример из 2313
                                       ; (в 2313 старший бит был не важен)
	out UCSRC,w                    ; формат: 1 старт, 8 данные, 2 стоп
	ldi w, (1<<RXEN)|(1<<TXEN)
	out UCSRB,w                    ; включить приёмник и передатчик
	;-- сбросить флаги и счётчики
	clr wait_flags
	clr fd_reg
	clr ld_reg
	;-- разрешаем прерывание от приёмника
	sbi UCSRB,RXCIE                ; включить прерывания от приёмника
	;-- сообщаем компу, что загрузились и ждём команду
	ldi w,0x0
	out UDR,w
	;-- разрешаем глобальные прерывания
	sei
	;-- ждём у моря погоды ---
Wait_data:
	rjmp Wait_data
;------------------------------------------
;-- процедуры задержки
Fast_Delay:
	nop
	nop
	nop
	nop
	nop
	dec  fd_reg          ; уменьшаем счётчик
	brne Fast_Delay      ; если счётчик не ноль следующий цикл
	ret                  ; если отсчитали нужную задержку - выходим
Low_Delay:
	ldi fd_reg,200       ; задержка 200 мкс
	rcall Fast_Delay
	dec ld_reg           ; уменьшаем счётчик
	brne Low_Delay       ; если счётчик не ноль следующий цикл
	ret                  ; если отсчитали нужную задержку - выходим
;------------------------------------------
;--- Обработчик прерывания от приёмника ---
RX_INT:
	in   w,UDR           ; читаем байт из приёмника в w
	;-- если хотябы 1 флаг поднят, то мы приняли данные, а не команду
	tst wait_flags       ; проверяем на ноль регистр флагов
	breq CommandHandle
	;-- если приняты данные, а не команды, то работаем по флагам
DataHandle:
TestFlag0:
	sbrs wait_flags,0    ; пропустить следующую команду, если установлен флаг 0
	rjmp TestFlag1
	;-- установить XA1/XA0/BS2/BS1 = биты [3:0] полученного по UART байта
	in  w2,PORTC         ; читаем текущие установленные значения выходов
	ori w2,0b00011110    ; поднимаем значения нужных битов ([4:1])
	sbrs w,0             ; если 0-й бит в принятом байте установлен - пропустить следующую команду
	andi w2,0b11111101   ; сбрасываем 1-й бит
	sbrs w,1             ; если 1-й бит в принятом байте установлен - пропустить следующую команду
	andi w2,0b11111011   ; сбрасываем 2-й бит
	sbrs w,2             ; если 2-й бит в принятом байте установлен - пропустить следующую команду
	andi w2,0b11110111   ; сбрасываем 3-й бит
	sbrs w,3             ; если 3-й бит в принятом байте установлен - пропустить следующую команду
	andi w2,0b11101111   ; сбрасываем 4-й бит
	out PORTC,w2         ; устанавливаем выходы в нужные значения
	rjmp ClearFlags 
TestFlag1:
	sbrs wait_flags,1    ; пропустить следующую команду, если установлен флаг 1
	rjmp TestFlag2
	;-- установить OE = бит [0] полученного по UART байта
	sbrc w,0             ; если 0-й бит в принятом байте сброшен - пропустить следующую команду
	rjmp SetDataOutput
SetDataInput:
	ser w
	out PORTB,w          ; устанавливаем подтяжки
	clr w
	out DDRB,w           ; переключаем порт на вход
	cbi PORTD, OE        ; сбрасываем OE (переключаем ноги микрухи на выход)
	rjmp ClearFlags
SetDataOutput:
	sbi PORTD, OE        ; поднимаем OE (переключаем ноги микрухи на вход)
	clr w
	out PORTB,w          ; обнуляем выходы
	ser w
	out DDRB,w           ; переключаем порт на выход
	rjmp ClearFlags
TestFlag2:
	sbrs wait_flags,2    ; пропустить следующую команду, если установлен флаг 2
	rjmp ClearFlags
	;-- установить Data = биты [7:0] полученного по UART байта
	out Data_Out,w
	;rjmp ClearFlags     ; этот обработчик флагов последний, так что эта команда не нужна
ClearFlags:
	clr wait_flags       ; сбрасываем флаги
	rjmp Send_Ok
	;-- если это не данные, а команды, то работаем с блоком переходов к обработчикам команд
CommandHandle:
	cpi  w,MinCommandNumber     ; сравниваем полученный байт с минимальным номером команды
	brlo Error_Command          ; номер команды меньше минимального - ошибка
	cpi  w,MaxCommandNumber + 1 ; сравниваем полученный байт с максимальным номером команды + 1
	brsh Error_Command          ; номер команды больше или равен максимальному +1
	subi w,MinCommandNumber     ; приводим ряд к начинающемуся с нуля
	rcall StartCommandCase      ; получаем в стек адрес следующей метки
StartCommandCase:
	pop  ZH                     ; извлекаем адрес из стека
	pop  ZL
	adiw ZL,7                   ; вычисляем начальный адрес блока переходов (он сдвинут на 7 от метки)
	add  ZL,w                   ; добавляем адрес команды в блоке переходов
	brcc NoCarry                ; пропускаем следующую команду, если не установился флаг переноса
	inc  ZH
NoCarry:
	ijmp                        ; переходим по вычисленному адресу в блок переходов
	;-- ниже расположен сам блок переходов
	rjmp SetInitState           ; 0x1 - установить выходы в начальное состояние
	rjmp EnterPM                ; 0x2 - переход в режим программирования
	rjmp ExitPM                 ; 0x3 - выход из режима программирования
	rjmp SetXABS                ; 0x4 - установить XA1/XA0/BS2/BS1
	rjmp SetOE                  ; 0x5 - установить OE
	rjmp GiveBS1PP              ; 0x6 - сформировать положительный импульс на BS1
	rjmp GivePagelPP            ; 0x7 - сформировать положительный импульс на Pagel
	rjmp GiveXTAL1PP            ; 0x8 - сформировать положительный импульс на XTAL1
	rjmp GiveWRNP               ; 0x9 - сформировать отрицательный импульс на WR
	rjmp SetData                ; 0xA - установить Data[7:0]
	rjmp ReadData               ; 0xB - прочитать Data[7:0]
	;-- конец блока переходов
Error_Command:
	ldi  w,0xFF                 ; сообщаем, что приняли неизвестную команду и выходим
	out  UDR,w
	reti
;--- Говорим компу, что всё сделали и ждём ещё команд
Send_Ok:
	;-- ждём, когда освободится буфер передатчика
	;-- здесь (и не только) по идее нужен ещё таймер для предотвращения зависания программы, в случае
	;-- если буфер не освобождается длительное время, но для тестовой версии сойдёт и так
	sbis UCSRA,UDRE
	rjmp Send_Ok
	ldi w,0x00                  ; отправляем признак готовности
	out UDR,w
	reti
;--- ОБРАБОТЧИКИ КОМАНД --------------------------
;--------------------------------------------------
;-- set initial state
;-- XA1 / XA0 / BS1 / WR = "0000", +5 = off, +12 = off
;-- (сигнал WR у нас инверсный по определению, Ctrl_5 инверсный по схемотехнике)
;-- XA1=0, XA0=0, BS1=0, WR=1, Ctrl_5=1, Ctrl_12=0
;-- RDY=0 (без подтяжки), BS2=0, PAG=0, OE=1, XTAL1=0
;-- DATA=0x0, все сигналы на выход, кроме RDY
SetInitState:
	;-- настраиваем PORTD (состояние - все, кроме PD2(OE), PD4(WR), PD7(+5) - нули, направление - все на выход)
	ldi w,0b10000000
	out PORTD,w
	ldi w,0b11111111
	out DDRD,w
	;-- настраиваем PORTC (состояние - все нули, направление - все на выход, кроме PC0(RDY))
	clr w
	out PORTC,w
	ldi w, 0b11111110
	out DDRC,w
	;-- настраиваем PORTB (состояние - все нули, направление - все на выход)
	clr w
	out PORTB,w
	ser w
	out DDRB,w
	rjmp Send_Ok        ; сообщаем о готовности
;-- enter program mode (включить +5В, минимум через 30 мкс включить +12В, минимум через 300 мкс можно подавать команды)
EnterPM:
	cbi PORTD, Ctrl_5   ; включаем +5 Вольт (PD7=0)
	ldi fd_reg,40       ; задержка 40 циклов (чуть больше 40 мкс)
	rcall Fast_Delay
	sbi PORTD, Ctrl_12  ; включаем +12 Вольт (PD6=1)
	ldi ld_reg,2        ; задержка 1 цикл (чуть больше 200 мкс)
	rcall Low_Delay
	sbi PORTD, OE       ; поднимаем OE
	sbi PORTD, WR       ; поднимаем WR
	ldi ld_reg,2        ; задержка 1 цикл (чуть больше 200 мкс)
	rcall Low_Delay
	rjmp Send_Ok        ; сообщаем о готовности
;-- exit program mode (выключить +12В, выключить +5В)
ExitPM:
	cbi PORTD, Ctrl_12  ; выключаем +12 Вольт (PD6=0)
	sbi PORTD, Ctrl_5   ; выключаем +5 Вольт (PD7=1)
	rjmp Send_Ok        ; сообщаем о готовности
;-- set XA1/XA0/BS2/BS1 (4 младших бита в следующем принятом байте)
SetXABS:
	sbr wait_flags,(1<<0) ; поднимаем 0-й флаг в регистре флагов (ожидание XA1/XA0/BS2/BS1)
	rjmp Send_Ok          ; сообщаем о готовности
;-- set OE line (младший бит в следующем принятом байте)
SetOE:	sbr wait_flags,(1<<1) ; поднимаем 2-й флаг в регистре флагов (ожидание OE)
	rjmp Send_Ok          ; сообщаем о готовности
;-- give BS1 positive pulse
GiveBS1PP:
	sbi PORTC, BS1        ; переключаем BS1 в 1
	ldi fd_reg,1          ; задержка 1 цикл (чуть больше 1 мкс)
	rcall Fast_Delay
	cbi PORTC, BS1        ; переключаем BS1 в 0
	rjmp Send_Ok          ; сообщаем о готовности
;-- give Pagel positive pulse
GivePagelPP:
	sbi PORTC, PAG        ; переключаем PAG в 1
	ldi fd_reg,1          ; задержка 1 цикл (чуть больше 1 мкс)
	rcall Fast_Delay
	cbi PORTC, PAG        ; переключаем PAG в 0
	rjmp Send_Ok          ; сообщаем о готовности
;-- give XTAL1 positive pulse
GiveXTAL1PP:
	sbi PORTD, XTAL1      ; переключаем XTAL1 в 1
	ldi fd_reg,1          ; задержка 1 цикл (чуть больше 1 мкс)
	rcall Fast_Delay
	cbi PORTD, XTAL1      ; переключаем XTAL1 в 0
	rjmp Send_Ok          ; сообщаем о готовности
;-- give WR negative pulse
GiveWRNP:
	cbi PORTD, WR         ; переключаем WR в 0
	ldi fd_reg,1          ; задержка 1 цикл (чуть больше 1 мкс)
	rcall Fast_Delay
	sbi PORTD, WR         ; переключаем WR в 1
	rjmp Send_Ok          ; сообщаем о готовности
;-- set Data[7:0]
SetData:
	sbr wait_flags,(1<<2) ; поднимаем 3-й флаг в регистре флагов (ожидание Data)
	rjmp Send_Ok          ; сообщаем о готовности
;-- read Data[7:0]
ReadData:
	in w,Data_In          ; читаем линии данных
	out UDR,w             ; отправляем в порт
	rjmp Send_Ok          ; сообщаем о готовности

[свернуть]

Готовая прошивка (*.hex). Осталось только залить эту прошивку в управляющий микроконтроллер (как вы помните, это можно сделать прямо через расположенный на плате разъём ICSP).

Работа

Вот так выглядит устройство с подключенным к нему чипом (который нужно «вылечить» или прошить):

параллельный программатор для avr в работе

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

Сам программатор подключен к компьютеру через продающийся на нашем сайте метровый переходник USB-to-UART на микросхеме cp2102.

Весь процесс работы с программатором сводится к передаче программатору нужной последовательности байт (команд). Сделать это можно через любую терминалку, умеющую работать в hex-режиме (я, естественно, пользуюсь своей терминалкой RH_Com, которую любой желающий может скачать вместе с исходниками с нашего сайта из раздела «Полезные программы для ПК»).

Ниже приведены примеры вариантов последовательностей для чипа ATtiny2313 (вместо BS2 для ATtiny2313 используется XA1). Байты нужно передавать по очереди, каждый отдельно, дождавшись ответа о выполнении предыдущей команды (все сразу отправлять нельзя):

Выполнение команды Chip Erase (стирание чипа и сброс lock-битов):
01 02 04 08 0A 80 08 09 03

Расшифровка приведённой последовательности

	0x01 - установить на выходах начальные значения
	0x02 - перейти в режим программирования
A	Load Command (загрузка команды в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x08 - младшие 4 бита ('1000') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0x80 - значения, которые нужно установить (команда '1000 0000' - chip erase)
A'	0x08 - сформировать положительный импульс на линии XTAL1
	0x09 - сформировать отрицательный импульс на линии WR
	0x03 - выйти из режима программирования

[свернуть]

Запись дефолтных значений в младший байт фьюзов:
01 02 04 08 0A 40 08 04 04 0A 64 08 04 00 09 03

Расшифровка приведённой последовательности

	0x01 - установить на выходах начальные значения
	0x02 - перейти в режим программирования
A	Load Command (загрузка команды в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x08 - младшие 4 бита ('1000') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0x40 - значения, которые нужно установить (команда '0100 0000' - write fuse bits)
A'	0x08 - сформировать положительный импульс на линии XTAL1
C	Load Data Byte (загрузка данных в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x04 - младшие 4 бита ('0100') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0x64 - значения, которые нужно установить (соответствует значению младшего байта фьюзов по-умолчанию)
C'	0x08 - сформировать положительный импульс на линии XTAL1
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x00 - младшие 4 бита ('0000') показывают, какие уровни нужно установить
	0x09 - сформировать отрицательный импульс на линии WR
	0x03 - выйти из режима программирования

[свернуть]

Запись дефолтных значений в старший байт фьюзов:
01 02 04 08 0A 40 08 04 04 0A DF 08 04 01 09 03

Расшифровка приведённой последовательности

	0x01 - установить на выходах начальные значения
	0x02 - перейти в режим программирования
A	Load Command (загрузка команды в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x08 - младшие 4 бита ('1000') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0x40 - значения, которые нужно установить (команда '0100 0000' - write fuse bits)
A'	0x08 - сформировать положительный импульс на линии XTAL1
C	Load Data Byte (загрузка данных в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x04 - младшие 4 бита ('0100') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0xDF - значения, которые нужно установить (соответствуют значению старшего байта фьюзов по-умолчанию)
C'	0x08 - сформировать положительный импульс на линии XTAL1
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x01 - младшие 4 бита ('0001') показывают, какие уровни нужно установить
	0x09 - сформировать отрицательный импульс на линии WR
	0x03 - выйти из режима программирования

[свернуть]

Чтение фьюзов и lock-битов:
01 02 04 08 0A 04 08 05 00 04 00 0B 04 09 0B 04 08 0B 04 01 0B 05 01 03

Расшифровка приведённой последовательности

	0x01 - установить на выходах начальные значения
	0x02 - перейти в режим программирования
A	Load Command (загрузка команды в программируемый чип)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x08 - младшие 4 бита ('1000') показывают, какие уровни нужно установить
	0x0A - установить уровни на линиях Data[7:0]
	0x04 - значения, которые нужно установить (команда '0000 0100' - read fuse and lock bits)
A`	0x08 - сформировать положительный импульс на линии XTAL1
	0x05 - установить нужный уровень на линии OE
	0x00 - младший бит ('0') показывает какой уровень нужно установить (порт данных программируемого чипа - на выход)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x00 - младшие 4 бита ('0000') показывают, какие уровни нужно установить
	0x0B - считать значения линий Data[7:0] (первый принятый после этой команды байт содержит Fuse Low Bits)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x09 - младшие 4 бита ('1001' вместо '0011') показывают, какие уровни нужно установить
	0x0B - считать значения линий Data[7:0] (первый принятый после этой команды байт содержит Fuse High Bits)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x08 - младшие 4 бита ('1000' вместо '0010') показывают, какие уровни нужно установить
	0x0B - считать значения линий Data[7:0] (первый принятый после этой команды байт содержит Extended Fuse Bits)
	0x04 - установить нужные уровни на линиях XA1 / XA0 / BS2 / BS1
	0x01 - младшие 4 бита ('0001') показывают, какие уровни нужно установить
	0x0B - считать значения линий Data[7:0] (первый принятый после этой команды байт содержит Lock Bits)
	0x05 - установить нужный уровень на линии OE
	0x01 - младший бит ('1') показывает какой уровень нужно установить (порт данных программируемого чипа - на вход)
	0x03 - выйти из режима программирования

[свернуть]

Аналогичным образом можно составить последовательности для выполнения всех возможных команд чипа (запись/чтение заданных адресов flash/EEPROM, чтение байтов сигнатуры, чтение калибровочной константы…) Остаётся только написать выполняющее все нужные последовательности ПО верхнего уровня и мы получим полноценный параллельный программатор для чипов AVR, позволяющий удобно и быстро прошивать не только отдельные байты, но и всю память чипов целиком. Возможно, в недалёком будущем я именно этим и займусь, ну а на сегодня — всё.

P.S. Лайки и комментарии могут значительно ускорить разработку софта верхнего уровня 🙂

P.P.S. Все до одного китайские чипы оказались живые, но с «неправильными» фьюзами.

Добавить комментарий