Всем привет, наткнулся на просторах интернета на интересную ссылку, так как автор выгрузил схему, код, прошивку и файлы протеуса то немного поигрался:
Вот ещё видео материал к статье:
Ну и надоело программно играться приобрёл себе регистр сдвига 74CH595, вот распиновка из даташита микросхемы в DIP корпусе:
Я уже тут писал как можно прошить Attiny13 при помощи Arduino, потом берём код из странички выше:
Фишкой такой схемы является малое потребление, сама по себе тинька довольно таки экономичная, вот график из даташита потребления на разных частотах и напряжении:
А вот для удобства сборки на макетке добавляю распиновку Attiny13 тоже в DIP корпусе:
Собираем схему:
// Подключение LCD HD44780 к AVR через регистр сдвига #include <avr/io.h> #include <util/delay.h> // Команды для управления портами LCD /* RS */ #define RS1 registr(0x01, 1) #define RS0 registr(0x01, 0) /* E */ #define E1 registr(0x02, 1) #define E0 registr(0x02, 0) /* D4 */ #define D41 registr(0x04, 1) #define D40 registr(0x04, 0) /* D5 */ #define D51 registr(0x08, 1) #define D50 registr(0x08, 0) /* D6 */ #define D61 registr(0x10, 1) #define D60 registr(0x10, 0) /* D7 */ #define D71 registr(0x20, 1) #define D70 registr(0x20, 0) // Функция установки курсора в указанную точку #define lcd_gotoxy(x, y) write_to_lcd(0x80|((x)+((y)*0x40)), 0) // Функция передачи данных в регистр void registr(unsigned char data, unsigned char WriteOrErase) { volatile static unsigned char tempdata = 0; if(WriteOrErase == 1) tempdata = (tempdata|data); else tempdata &= ~(data); PORTB &= ~(1 << PB1); // ST_CP 0 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x80)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x40)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x20)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x10)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x08)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x04)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x02)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x01)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB |= (1 << PB1); // ST_CP 1 } // Функция передачи данных или команды в LCD void write_to_lcd(char p, unsigned char rs) { if(rs == 1) RS1; else RS0; E1; if(p&0x10) D41; else D40; if(p&0x20) D51; else D50; if(p&0x40) D61; else D60; if(p&0x80) D71; else D70; E0; _delay_ms(2); E1; if(p&0x01) D41; else D40; if(p&0x02) D51; else D50; if(p&0x04) D61; else D60; if(p&0x08) D71; else D70; E0; _delay_ms(2); } // Функция инициализации LCD void lcd_init(void) { write_to_lcd(0x02, 0); // Курсор в верхней левой позиции write_to_lcd(0x28, 0); // Шина 4 бит, LCD - 2 строки write_to_lcd(0x0C, 0); // Разрешаем вывод изображения, курсор не виден write_to_lcd(0x01, 0); // Очищаем дисплей } // Функция вывода строки void lcd_puts(char *str) { unsigned char i = 0; while(str[i]) write_to_lcd(str[i++], 1); } // Функция вывода переменной void lcd_num_to_str(unsigned int value, unsigned char nDigit) { switch(nDigit) { case 4: write_to_lcd((value/1000)+'0', 1); case 3: write_to_lcd(((value/100)%10)+'0', 1); case 2: write_to_lcd(((value/10)%10)+'0', 1); case 1: write_to_lcd((value%10)+'0', 1); } } // Функция чтения АЦП int readADC(unsigned char ch) { ADMUX = ch; // Выбираем канал АЦП ADCSRA |= (1 << ADSC); // Запускаем преобразование while((ADCSRA & (1 << ADSC))); // Ждем окончания преобразования return(ADC*11/2); // Возвращаем значение АЦП } int main(void) { DDRB = 0b00000111; // Настраиваем входы/выходы ADCSRA |= (1 << ADEN) // Разрешение АЦП |(1 << ADPS2)|(1 << ADPS1); // Предделитель на 64 ACSR |= (1 << ACD); // Выключаем аналаговый компаратор DIDR0 |= (1 << ADC3D)|(1 << ADC2D); // Отключаем неиспользуемые цифровые входы lcd_init(); // Инициализация дисплея write_to_lcd(0x01, 0); // Очищаем дисплей lcd_gotoxy(0, 0); // Выводим строки на LCD lcd_puts("U1 = . V"); lcd_gotoxy(0, 1); lcd_puts("U2 = . V"); while(1) { lcd_gotoxy(5, 0); lcd_num_to_str(readADC(2)/100, 2); // Выводим данные АЦП1 на LCD lcd_gotoxy(8, 0); lcd_num_to_str(readADC(2)%100/10, 1); lcd_gotoxy(5, 1); lcd_num_to_str(readADC(3)/100, 2); // Выводим данные АЦП2 на LCD lcd_gotoxy(8, 1); lcd_num_to_str(readADC(3)%100/10, 1); _delay_ms(50); } }
И вставляем в Arduino IDE, жмём компилировать и что мы видим:
Среда "проглотила" код, а это значит что можно запрограммировать тиньку при помощи дуинки не имея никаких ISP программаторов.
Пока игрался с протеусом немного модифицировал код автора статьи:// Подключение LCD HD44780 к AVR через регистр сдвига #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include <avr/sleep.h> #include <avr/power.h> ISR(ADC_vect) // обязательно должно быть { } // Команды для управления портами LCD /* RS */ #define RS1 registr(0x01, 1) #define RS0 registr(0x01, 0) /* E */ #define E1 registr(0x02, 1) #define E0 registr(0x02, 0) /* D4 */ #define D41 registr(0x04, 1) #define D40 registr(0x04, 0) /* D5 */ #define D51 registr(0x08, 1) #define D50 registr(0x08, 0) /* D6 */ #define D61 registr(0x10, 1) #define D60 registr(0x10, 0) /* D7 */ #define D71 registr(0x20, 1) #define D70 registr(0x20, 0) // Функция установки курсора в указанную точку #define lcd_gotoxy(x, y) write_to_lcd(0x80|((x)+((y)*0x40)), 0) // Функция передачи данных в регистр void registr(byte data, byte WriteOrErase) { volatile static byte tempdata = 0; if(WriteOrErase == 1) tempdata = (tempdata|data); else tempdata &= ~(data); PORTB &= ~(1 << PB1); // ST_CP 0 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x80)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x40)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x20)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x10)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x08)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x04)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x02)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB &= ~(1 << PB2); // SH_CP 0 if(tempdata & 0x01)PORTB |= (1 << PB0); else PORTB &= ~(1 << PB0); PORTB |= (1 << PB2); // SH_CP 1 PORTB |= (1 << PB1); // ST_CP 1 } // Функция передачи данных или команды в LCD void write_to_lcd(char p, byte rs) { if(rs == 1) RS1; else RS0; E1; if(p&0x10) D41; else D40; if(p&0x20) D51; else D50; if(p&0x40) D61; else D60; if(p&0x80) D71; else D70; E0; _delay_ms(2); E1; if(p&0x01) D41; else D40; if(p&0x02) D51; else D50; if(p&0x04) D61; else D60; if(p&0x08) D71; else D70; E0; _delay_ms(2); } // Функция инициализации LCD void lcd_init(void) { write_to_lcd(0x02, 0); // Курсор в верхней левой позиции write_to_lcd(0x28, 0); // Шина 4 бит, LCD - 2 строки write_to_lcd(0x0C, 0); // Разрешаем вывод изображения, курсор не виден write_to_lcd(0x01, 0); // Очищаем дисплей } // Функция вывода строки void lcd_puts(char *str) { byte i = 0; while(str[i]) write_to_lcd(str[i++], 1); } // Функция вывода переменной void lcd_num_to_str(unsigned int value, byte nDigit) { switch(nDigit) { //case 4: // для чего мне 4 цифры, мне и двух хватит // write_to_lcd((value/1000)+'0', 1); //case 3: // write_to_lcd(((value/100)%10)+'0', 1); case 2: write_to_lcd(((value/10)%10)+'0', 1); case 1: write_to_lcd((value%10)+'0', 1); } } // Функция чтения АЦП unsigned int readADC(byte Channel) { uint8_t l,h; //ADMUX = (1<<REFS0) | pin & 3; //Setup ADC ADMUX = (ADMUX & _BV(REFS0)) | Channel & 3; //Setup ADC, preserve REFS0 ADCSRA |= _BV(ADSC); while(ADCSRA & (1<<ADSC)); //Wait for conversion l = ADCL; //Read and return 10 bit result h = ADCH; return (h << 8)|l; // Возвращаем значение АЦП } unsigned int analogNoiseReducedRead(byte analogChannel) { unsigned int reading; ADCSRA |= _BV( ADIE ); //Set ADC interrupt set_sleep_mode(SLEEP_MODE_ADC); //Set sleep mode reading = readADC(analogChannel); //Start reading sleep_enable(); //Enable sleep do { //Loop until reading is completed sei(); //Enable interrupts sleep_mode(); //Go to sleep cli(); //Disable interrupts } while(((ADCSRA&(1<<ADSC))!= 0)); //Loop if the interrupt that woke the cpu was something other than the ADC finishing the reading sleep_disable(); //Disable sleep ADCSRA &= ~ _BV( ADIE ); //Clear ADC interupt sei(); //Enable interrupts return(reading); } unsigned int readADCOversampled(byte analogChannel) { // оверсемплинг для увеличения разрядности АЦП unsigned long aSum = 0; for(int i = 512; i > 0; i--){ // делаем 512 выборок АЦП aSum = aSum + analogNoiseReducedRead(analogChannel); } return aSum >> 7; // получается что функция возвращает 0...4092 } int main(void) { DDRB = 0b00000111; // Настраиваем входы/выходы ADCSRA |= (1 << ADEN) // Разрешение АЦП |(1 << ADPS2)|(1 << ADPS1); // Предделитель на 64 ACSR |= (1 << ACD); // Выключаем аналаговый компаратор DIDR0 |= (1 << ADC3D)|(1 << ADC2D); // Отключаем неиспользуемые цифровые входы lcd_init(); // Инициализация дисплея //write_to_lcd(0x01, 0); // Очищаем дисплей, уже есть lcd_gotoxy(0, 0); // Выводим строки на LCD lcd_puts("V1 . "); // использую 8 символов и 2 строчки lcd_gotoxy(0, 1); lcd_puts("V2 . "); while(1) { //ADMUX |= _BV(REFS0); unsigned int v1 = readADCOversampled(2); //ADMUX &= ~_BV(REFS0); unsigned int v2 = readADCOversampled(3); if(v1 > 3999){ // ну зачем видеть больше 39.99? v1 = 3999; // так прикольней } if(v2 > 3999){ v2 = 3999; } lcd_gotoxy(3, 0); lcd_num_to_str(v1/100, 2); // Выводим данные АЦП1 на LCD lcd_gotoxy(6, 0); lcd_num_to_str(v1%100, 2); lcd_gotoxy(3, 1); lcd_num_to_str(v2/100, 2); // Выводим данные АЦП2 на LCD lcd_gotoxy(6, 1); lcd_num_to_str(v2%100, 2); //_delay_ms(25); // ненужно ждать, оверсемплинг довольно таки // сильно тормозит камень } }
Добавив несколько функций и слегка доработав код, в частности урезал отображаемую информация под бюджетный дисплей 8 символов 2 строчки.
Итак, прошьём и посмотрим как работает:
На фото параллельно питанию тиньки стоит конденсатор на 220 мкФ - для уменьшения дрожания сотых.
Итак как видим работает, осталось рассчитать делитель и собрать на текстолите.
В отличии от кода автора статьи у меня выводиться как десятые так и сотые вольта, погрешность ожидаю получить не больше чем 0.5 % что сопоставимо с обычным китайским мультиметром.Фишкой такой схемы является малое потребление, сама по себе тинька довольно таки экономичная, вот график из даташита потребления на разных частотах и напряжении:
Как видим на частоте в 9.6 MHz и питании в 5 В тиня будет потреблять примерно 2-3 мА а при напряжении в 3.3 В - 1-2 мА, причём на той же частоте. Если же частоту опустить до 1.2 МГц выставив фьюзы чтобы был включен делитель на 8, а как известно что потребление напрямую зависит от частоты камня то то при напряжении питания в 5 В тиня судя по графику будет кушать совсем мало.
Только что замерял сколько кушает миллиампер данная схема с регистром сдвига и LCD на основе модуля HD44780 (без подсветки) - 5 мА, неплохо должен сказать.
Комментариев нет:
Отправить комментарий