From cff29240303d385ea5e4cd2d6016774662218b4d Mon Sep 17 00:00:00 2001 From: Guido Socher Date: Sat, 22 May 2010 00:00:00 +0200 Subject: [PATCH] 0.6.0 this is the first version for the new hardware --- Makefile | 120 ++++++++++++++++ README.htm | 136 ++++++++++++++++++ analog.c | 231 ++++++++++++++++++++++++++++++ analog.h | 18 +++ dac.c | 53 +++++++ dac.h | 9 ++ hardware_settings.h | 56 ++++++++ hardware_settings.h-22V | 56 ++++++++ hardware_settings.h-30V | 56 ++++++++ kbd.c | 105 ++++++++++++++ kbd.h | 14 ++ lcd.c | 273 +++++++++++++++++++++++++++++++++++ lcd.h | 103 ++++++++++++++ lcd_hw.h | 45 ++++++ main.c | 309 ++++++++++++++++++++++++++++++++++++++++ test_dac.c | 128 +++++++++++++++++ test_lcd.c | 91 ++++++++++++ winload.bat | 10 ++ winmake.bat | 14 ++ winsetfuse.bat | 8 ++ 20 files changed, 1835 insertions(+) create mode 100644 Makefile create mode 100644 README.htm create mode 100644 analog.c create mode 100644 analog.h create mode 100644 dac.c create mode 100644 dac.h create mode 100644 hardware_settings.h create mode 100644 hardware_settings.h-22V create mode 100644 hardware_settings.h-30V create mode 100644 kbd.c create mode 100644 kbd.h create mode 100644 lcd.c create mode 100644 lcd.h create mode 100644 lcd_hw.h create mode 100644 main.c create mode 100644 test_dac.c create mode 100644 test_lcd.c create mode 100644 winload.bat create mode 100644 winmake.bat create mode 100644 winsetfuse.bat diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..744b22a --- /dev/null +++ b/Makefile @@ -0,0 +1,120 @@ +# makefile for digitial dc power supply, written by guido socher +MCU=atmega8 +DUDECPUTYPE=m8 +# === Edit this and enter the correct device/com-port: +# linux (plug in the avrusb500 and type dmesg to see which device it is): +LOADCMD=avrdude -P /dev/ttyUSB0 + +# mac (plug in the programer and use ls /dev/tty.usbserial* to get the name): +#LOADCMD=avrdude -P /dev/tty.usbserial-A9006MOb + +# windows (check which com-port you get when you plugin the avrusb500): +#LOADCMD=avrdude -P COM4 + +# All operating systems: if you have set the default_serial paramter +# in your avrdude.conf file correctly then you can just use this +# and you don't need the above -P option: +#LOADCMD=avrdude +# === end edit this +# +LOADARG=-p $(DUDECPUTYPE) -c stk500v2 -e -U flash:w: + + +CC=avr-gcc +OBJCOPY=avr-objcopy +# optimize for size: +CFLAGS=-g -mmcu=$(MCU) -Wall -W -Os -mcall-prologues +# #------------------- +.PHONY: test_lcd test_dac all main +# +all: main.hex test_lcd.hex test_dac.hex +# +main: main.hex +# +test_lcd: test_lcd.hex + echo "OK" +# +test_dac: test_dac.hex + echo "OK" +#------------------- +size: + avr-size *.elf +help: + @echo "Usage: make help" + @echo " Print this help" + @echo " " + @echo "Usage: make all|load|rdfuses|fuses" + @echo " program using the avrdude programmer" + @echo " " + @echo "Usage: make clean" + @echo " delete all generated files except the pre-compiled ones" + @echo "Test programs:" + @echo "Usage: make test_lcd|load_test_lcd|test_dac|load_test_dac" + @echo " compile and load test programs" +#------------------- +main.hex: main.elf + $(OBJCOPY) -R .eeprom -O ihex main.elf main.hex + avr-size main.elf + @echo " " + @echo "Expl.: data=initialized data, bss=uninitialized data, text=code" + @echo " " +main.elf: main.o dac.o lcd.o analog.o kbd.o + $(CC) $(CFLAGS) -o main.elf -Wl,-Map,main.map main.o dac.o lcd.o analog.o kbd.o +main.o: main.c dac.h kbd.h lcd.h lcd_hw.h analog.h hardware_settings.h + $(CC) $(CFLAGS) -Os -c main.c +#------------------- +test_lcd.hex: test_lcd.elf + $(OBJCOPY) -R .eeprom -O ihex test_lcd.elf test_lcd.hex +test_lcd.elf: test_lcd.o lcd.o kbd.o + $(CC) $(CFLAGS) -o test_lcd.elf -Wl,-Map,test_lcd.map test_lcd.o lcd.o kbd.o +test_lcd.o: test_lcd.c lcd.h lcd_hw.h kbd.h + $(CC) $(CFLAGS) -Os -c test_lcd.c +#------------------- +test_dac.hex: test_dac.elf + $(OBJCOPY) -R .eeprom -O ihex test_dac.elf test_dac.hex +test_dac.elf: test_dac.o lcd.o kbd.o dac.o + $(CC) $(CFLAGS) -o test_dac.elf -Wl,-Map,test_dac.map test_dac.o lcd.o kbd.o dac.o +test_dac.o: test_dac.c lcd.h lcd_hw.h kbd.h dac.h + $(CC) $(CFLAGS) -Os -c test_dac.c +#------------------- +lcd.o : lcd.c lcd.h lcd_hw.h + $(CC) $(CFLAGS) -Os -c lcd.c +#------------------- +analog.o : analog.c analog.h hardware_settings.h + $(CC) $(CFLAGS) -Os -c analog.c +#------------------- +dac.o : dac.c dac.h + $(CC) $(CFLAGS) -Os -c dac.c +#------------------- +kbd.o : kbd.c kbd.h + $(CC) $(CFLAGS) -Os -c kbd.c +#------------------- +load: main.hex + $(LOADCMD) $(LOADARG)main.hex +# +load_test_lcd: test_lcd.hex + $(LOADCMD) $(LOADARG)test_lcd.hex +# +load_test_dac: test_dac.hex + $(LOADCMD) $(LOADARG)test_dac.hex +# +#------------------- +# fuse byte settings: +# Atmel AVR ATmega8 +# Fuse Low Byte = 0xe1 (1MHz internal), 0xe3 (4MHz internal), 0xe4 (8MHz internal) +# Fuse High Byte = 0xd9 +# Factory default is 0xe1 for low byte and 0xd9 for high byte +# Check this with make rdfuses +rdfuses: + $(LOADCMD) -p $(DUDECPUTYPE) -c stk500v2 -v -q +# use internal RC oscillator 8 Mhz (lf=0xe4 hf=0xd9) +fuses: + $(LOADCMD) -p $(DUDECPUTYPE) -c stk500v2 -u -v -U lfuse:w:0xe4:m + $(LOADCMD) -p $(DUDECPUTYPE) -c stk500v2 -u -v -U hfuse:w:0xd9:m +fuse: + $(LOADCMD) -p $(DUDECPUTYPE) -c stk500v2 -u -v -U lfuse:w:0xe4:m + $(LOADCMD) -p $(DUDECPUTYPE) -c stk500v2 -u -v -U hfuse:w:0xd9:m +#------------------- +clean: + rm -f *.o *.map *.elf test*.hex main.hex +#------------------- diff --git a/README.htm b/README.htm new file mode 100644 index 0000000..d0b5187 --- /dev/null +++ b/README.htm @@ -0,0 +1,136 @@ +

Tuxgraphics Digital DC Power Supply Unit

+
+
+Compile the software and program the microcontroller:
+
+1) edit and adapt the file hardware_settings.h 
+   (calibration and selection of the right hardware type).
+
+2) load and compile:
+make 
+make load
+
+3) set the fuse bytes to 8MHz internal:
+make fuse
+
+This should result in the following settings:
+
+avrdude: Device signature = 0x1e9307
+avrdude: safemode: lfuse reads as E4
+avrdude: safemode: hfuse reads as D9
+
+
+The make fuse needs to be done only once
+unless you change the Atmega8 chip. make/make load
+need to be repeated everytime you change something
+in the software (e.g update of hardware_settings.h). 
+
+The poweresupply must be powered up during programming
+but nothing should be connected to the output. Make
+sure that you do not accidently press any button
+during the programming (loading the software into the uC).
+
+Compile under windows
+=====================
+Please use the provided Makefile.
+
+There are as well 3 batch files which you can use
+to run the makefile. To use them edit them and change
+the path as per your avr-gcc installation on your PC.
+After that run them by double-clickin in the file manager
+on those batch files.
+
+ winmake.bat  -- comile, this is mandatory to use
+
+ winload.bat  -- load software using avrdude, may also use
+                 some other means of loading the softare. In that
+                 case you do not need to use this batch file.
+
+ winsetfuse.bat -- set fuse bytes using avrdude, may also use
+                   some other means of loading the softare. In that
+                   case you do not need to use this batch file.
+
+Calibration
+===========
+To calibrate the display edit the file hardware_settings.h
+Here you can change also the settings between the 30V and the 22V version.
+
+In general you should only need to change 
+U_DIVIDER and I_RESISTOR by very small amounts. 
+
+---------------------------------------------------------------
+
+Overview
+========
+This is the software for the tuxgraphics.org electronic digital DC power supply. 
+A microncontroller controlled bench DC power supply unit. 
+
+All of the control logic is implemented in software. This
+saves a lot of parts compared to conventional lab-power 
+supplies. This means it is cheaper, easier to build and
+offers more functionallity.
+
+Note however that this means also that the short circuit
+protection is implemented in software. So be careful when
+you change something in the software. If the microcontroller 
+is not running properly due to a software fault then there
+might not be any short circuit protection.
+
+
+The software is prepared for 2 versions:
+- 0-22V 0-2.5A
+- 0-30V 0-2A
+
+It is possible to build a different version without major
+re-design. 
+
+---------------------------------------------------------------
+How the software works
+======================
+
+I have added a lot more comments to the software than usual. 
+It should be possible even for somebody with little experience in
+C programming to understand how this software works.
+
+The circuit uses as internal units ADC steps. All external values
+(Volt, Ampere) are converted to steps of the analog to digital 
+converter (ADC). When you change the voltage then this is first
+converted to ADC steps and then it will be further processed.
+
+
+main.c -- this is the main program. All initialization starts here.
+ It contains a infinite while loop which will execute the slow tasks
+ one by one: 
+   + Convert ADC results to display values
+   + Update the LCD
+   + Check push buttons
+ 
+analog.c -- the analog to digital converter and the main control loop
+         for the power supply. Everything is interrupt based here as
+         it needs to be fast.
+         Voltage control, current limitation and short circuit protection
+         are all implemented here.
+
+dac.c -- the digital to analog converter. Initalized from main.c but
+         used exclusivly from analog.c
+
+kbd.c -- the keyboard driver
+
+lcd.c -- the LCD driver. This is a special version which will not need
+         the rw pin of the display. It uses instead an internal timer
+         which should be long enough for the display to finish its task.
+         The RW pin which is normally used to poll the display to see
+         if it is ready is not needed.
+
+-------------------------------------------------------------------
+Copyright: GPL V2
+Author: Guido Socher
+Homepage: http://www.tuxgraphics.org/electronics/
+-------------------------------------------------------------------
+Change history:
+digitaldcpower-0.6.0 -- 2010-05-22 first version of the new power V3.
+                        This version is made for a new type of hardware
+			and will not run on any previous hardware version.
+
+-------------------------------------------------------------------
+
diff --git a/analog.c b/analog.c new file mode 100644 index 0000000..eedeb29 --- /dev/null +++ b/analog.c @@ -0,0 +1,231 @@ +/* vim: set sw=8 ts=8 si : */ +/********************************************* +* Author: Guido Socher, Copyright: GPL +* +* Digital analog conversion of channel ADC0 and ADC1 in +* free running mode. +**********************************************/ +#include +#include +#include +#include +#include "dac.h" +#include "hardware_settings.h" + + +//debug LED: +// set output to VCC, red LED off +#define LEDOFF PORTD|=(1<-4) tmp=0; + currentcontrol=40; // I control + if (analog_result[1]>target_val[1]){ + tmp=-20; + currentcontrol=0; // U control + } + }else{ + // if we are in current control then we can only go + // down (tmp is negative). To increase the current + // we come here to voltage control. We must slowly + // count up. + tmp=1 + target_val[1] - analog_result[1]; // voltage diff + // + if (currentcontrol){ + currentcontrol--; + if (currentcontrol%8==0){ + // slowly up, 20 will become 1 further down + if (tmp>0) tmp=20; + }else{ + tmp=0; + } + } + } + if (tmp==0){ + return; // nothing to change + } + if (tmp> -5 && tmp<5){ // avoid LSB bouncing if we are close + if (tmp>0){ + ptmp++; + tmp=0; + if (ptmp>1){ + tmp=1; + ptmp=0; + } + } + if (tmp<0){ + ptmp--; + tmp=0; + if (ptmp<-1){ + tmp=-1; + ptmp=0; + } + } + } + // put a cap on increase + if (tmp>1){ + tmp=1; + } + // put a cap on decrease + if (tmp<-1){ + tmp=-1; + } + dac_val+=tmp; + if (dac_val>0xFFF){ + dac_val=0xFFF; //max, 12bit + } + if (dac_val<400){ // the output is zero below 400 due to transistor threshold + dac_val=400; + } + dac(dac_val); +} +/* the following function will be called when analog conversion is done. + * It will be called every 13th cycle of the converson clock. At 8Mhz + * and a ADPS factor of 64 this is: ((8000 kHz/ 64) / 13)= 9.6KHz intervalls + * + * We do 4 fold oversampling as explained in atmel doc AVR121. + */ +//SIGNAL(SIG_ADC) { +ISR(ADC_vect) { + uint8_t i=0; + uint8_t adlow; + int16_t currentadc; + static uint8_t channel=0; + static uint8_t chpos=0; + // raw 10bit values: + static int16_t raw_analog_u_result[8]; + int16_t new_analog_u_result=0; + adlow=ADCL; // read low first !! + currentadc=(ADCH<<8)|adlow; + // toggel the channel between 0 an 1. This will however + // not effect the next conversion as that one is already + // ongoing. + channel^=1; + // 2.56V ref + ADMUX=(1<>1; // 11bit + // mean value: + analog_result[1]=(new_analog_u_result+analog_result[1])/2; + }else{ + analog_result[0]=currentadc; // 10bit + } + // short circuit protection does not use the over sampling results + // for speed reasons. + // short circuit protection, current is 10bit ADC + if (channel==0 && currentadc > SH_CIR_PROT){ + dac_val=400; + dac(dac_val); + currentcontrol=20; + return; + } + // + if (channel==1){ + // only after full measurement cycle + control_loop(); + } + // end of interrupt handler +} + diff --git a/analog.h b/analog.h new file mode 100644 index 0000000..d3032ec --- /dev/null +++ b/analog.h @@ -0,0 +1,18 @@ +/* vim: set sw=8 ts=8 si : */ +/************************************************************************* + Title : C include file for analog conversion + Target: any AVR device + Copyright: GPL +***************************************************************************/ +#ifndef ANALOG_H +#define ANALOG_H + +/* You must enable interrupt with sei() in the main program */ +extern void init_analog(void); +/* get the result of an analog conversion */ +extern int16_t getanalogresult(uint8_t channel); +extern void set_target_adc_val(uint8_t item,int16_t val); +extern uint8_t is_current_limit(void); +extern int16_t get_dacval(void); + +#endif /* ANALOG_H */ diff --git a/dac.c b/dac.c new file mode 100644 index 0000000..6b40674 --- /dev/null +++ b/dac.c @@ -0,0 +1,53 @@ +/* vim: set sw=8 ts=8 si : */ +/********************************************* +* Author: Guido Socher, Copyright: GPL +* +* Digital to analog converter using a R-2R leadder (7bit) +* and PWM (5bit) +**********************************************/ +#include + +// this dac can do 12 bit resolution: bit 0-4=pwm, bit 5-11=R-2R leadder +void dac(uint16_t value){ + //OCR1AH=0; + OCR1AL=value&0x1F; // lower 5 bits + value=value>>(5-2); + // r2r ladder is pd2 to pd7 and pb0 + PORTD=(PORTD&0x3)|(value&0xfc); + value=value>>8; + if (value){ // the MSB in the dac + PORTB|= (1< 0 Volt output) + OCR1AH=0; + OCR1AL=0; +} + + diff --git a/dac.h b/dac.h new file mode 100644 index 0000000..ded756e --- /dev/null +++ b/dac.h @@ -0,0 +1,9 @@ +/* vim: set sw=8 ts=8 si : */ +#ifndef DAC_H +#define DAC_H + +extern void init_dac(void); +// set a value: +extern void dac(uint16_t value); + +#endif diff --git a/hardware_settings.h b/hardware_settings.h new file mode 100644 index 0000000..6adafc1 --- /dev/null +++ b/hardware_settings.h @@ -0,0 +1,56 @@ +/* + * Hardware settings for the digital dc power supply + * http://www.tuxgraphics.org/electronics/ + * + * In this file you can: + * - calibrate the ampere and voltmeter + * - choose your hardware type: 22V 2.5A or 30V 2.0A + * + * The ADC has a resolution of 11 bit = values from 0-2047 + */ +#ifndef CAL_HW_H +#define CAL_HW_H + + +/* ================= uncomment this section for the model 22V 2.5A */ + +#define U_MAX 220 +#define I_MAX 250 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4] (calibrate the voltmeter, lower value=higher output) +#define U_DIVIDER 10.75 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +#define I_RESISTOR 0.79 +// short circuit protection limit (do not change unless you know what you do): +// 850=2.85A= (2.85A * 1023 * 0.75 / 2.56 ) +#define SH_CIR_PROT 850 + +/* ================= uncomment this section for the model 30V 2.0A */ + + +//#define U_MAX 300 +//#define I_MAX 200 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +//#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4], you can calibrate here the voltmeter: +//#define U_DIVIDER 13.24 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +//#define I_RESISTOR 0.78 + +// short circuit protection limit (do not change unless you know what you do): +// 690=2.30A= (2.30A * 1023 * 0.75 / 2.56 ) +//#define SH_CIR_PROT 690 + +#endif //CAL_HW_H + diff --git a/hardware_settings.h-22V b/hardware_settings.h-22V new file mode 100644 index 0000000..6adafc1 --- /dev/null +++ b/hardware_settings.h-22V @@ -0,0 +1,56 @@ +/* + * Hardware settings for the digital dc power supply + * http://www.tuxgraphics.org/electronics/ + * + * In this file you can: + * - calibrate the ampere and voltmeter + * - choose your hardware type: 22V 2.5A or 30V 2.0A + * + * The ADC has a resolution of 11 bit = values from 0-2047 + */ +#ifndef CAL_HW_H +#define CAL_HW_H + + +/* ================= uncomment this section for the model 22V 2.5A */ + +#define U_MAX 220 +#define I_MAX 250 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4] (calibrate the voltmeter, lower value=higher output) +#define U_DIVIDER 10.75 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +#define I_RESISTOR 0.79 +// short circuit protection limit (do not change unless you know what you do): +// 850=2.85A= (2.85A * 1023 * 0.75 / 2.56 ) +#define SH_CIR_PROT 850 + +/* ================= uncomment this section for the model 30V 2.0A */ + + +//#define U_MAX 300 +//#define I_MAX 200 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +//#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4], you can calibrate here the voltmeter: +//#define U_DIVIDER 13.24 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +//#define I_RESISTOR 0.78 + +// short circuit protection limit (do not change unless you know what you do): +// 690=2.30A= (2.30A * 1023 * 0.75 / 2.56 ) +//#define SH_CIR_PROT 690 + +#endif //CAL_HW_H + diff --git a/hardware_settings.h-30V b/hardware_settings.h-30V new file mode 100644 index 0000000..6a07954 --- /dev/null +++ b/hardware_settings.h-30V @@ -0,0 +1,56 @@ +/* + * Hardware settings for the digital dc power supply + * http://www.tuxgraphics.org/electronics/ + * + * In this file you can: + * - calibrate the ampere and voltmeter + * - choose your hardware type: 22V 2.5A or 30V 2.0A + * + * The ADC has a resolution of 11 bit = values from 0-2047 + */ +#ifndef CAL_HW_H +#define CAL_HW_H + + +/* ================= uncomment this section for the model 22V 2.5A */ + +//#define U_MAX 220 +//#define I_MAX 250 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +//#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4] (calibrate the voltmeter, lower value=higher output) +//#define U_DIVIDER 10.75 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +//#define I_RESISTOR 0.79 +// short circuit protection limit (do not change unless you know what you do): +// 850=2.85A= (2.85A * 1023 * 0.75 / 2.56 ) +//#define SH_CIR_PROT 850 + +/* ================= uncomment this section for the model 30V 2.0A */ + + +#define U_MAX 300 +#define I_MAX 200 + +// internal adc ref voltage (should be 2.56V, can vary from uC to uC) +#define ADC_REF 2.56 + +// the divider R3/R4 [(R3+R4)/R4], you can calibrate here the voltmeter: +#define U_DIVIDER 13.24 + +// the shunt for current measurement, you can calibrate here the +// amperemeter. +// 2*1.5Ohm 3W=0.75: +#define I_RESISTOR 0.78 + +// short circuit protection limit (do not change unless you know what you do): +// 690=2.30A= (2.30A * 1023 * 0.75 / 2.56 ) +#define SH_CIR_PROT 690 + +#endif //CAL_HW_H + diff --git a/kbd.c b/kbd.c new file mode 100644 index 0000000..dc41734 --- /dev/null +++ b/kbd.c @@ -0,0 +1,105 @@ +/* vim: set sw=8 ts=8 si : */ +/********************************************* +* Author: Guido Socher, Copyright: GPL +* +* read the keyboard +**********************************************/ +#include +#define F_CPU 8000000UL // 4 MHz +#include + + +// it looks like output port settings need time to propagate. Maybe +// caused by input capacitors on the lcd which connect to the same ports. +static void kbd_wait(void){ + _delay_ms(7.5); +} + +// kbd connection: +// U+=PC3, U-=PC4, I+,store=PB2, I-=PC5 +// common input wires: PB5, PB3 for store +// PC3, PC4, PB2, PC5 are already initialized as output lines +// by the LCD driver code +void init_kbd(void) +{ + // init lcd sets those already so we comment them out here: + /* + DDRC|=(1<0){ + (*u)--; + if((*u)<0){ + (*u)=0; + } + return(1); + } + return(0); +} + +uint8_t check_i_button(int16_t *i) +{ + // check I+ button: + PORTC|=(1<0){ + (*i)--; + if((*i)<0){ + (*i)=0; + } + return(1); + } + return(0); +} + +uint8_t check_store_button(void) +{ + // check store button: + PORTB&=~(1< +#include +#define F_CPU 8000000UL // 8 MHz +#include + +#include "lcd_hw.h" +#include "lcd.h" + +/* compatibilty macros for old style */ +#ifndef cbi +#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#endif + +#ifndef sbi +#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) +#endif + + +/* +** constants/macros +*/ + +#define lcd_e_high() sbi(LCD_E_PORT, LCD_E_PIN) +#define lcd_e_low() cbi(LCD_E_PORT, LCD_E_PIN) + +#define lcd_cmd_mode() cbi(LCD_RS_PORT, LCD_RS_PIN) /* RS=0 command mode */ +#define lcd_data_mode() sbi(LCD_RS_PORT, LCD_RS_PIN) /* RS=1 data mode */ +#define lcd_data_port_out() { /* defines all data pins as output */ \ + sbi(LCD_DATA_DDR_D7,LCD_DATA_PIN_D7);\ + sbi(LCD_DATA_DDR_D6,LCD_DATA_PIN_D6);\ + sbi(LCD_DATA_DDR_D5,LCD_DATA_PIN_D5);\ + sbi(LCD_DATA_DDR_D4,LCD_DATA_PIN_D4);\ + } + +#if LCD_LINES==1 +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_1LINE +#else +#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_2LINES +#endif + +/* +** function prototypes +*/ +static void lcd_e_toggle(void); +static void lcd_out_high(uint8_t d); +static void lcd_out_low(uint8_t d); + +/* +** local functions +*/ + +void lcd_delay_ms(uint8_t ms) +// delay for a minimum of +{ + while(ms){ + _delay_ms(0.96); + ms--; + } +} + +static void lcd_out_low(uint8_t d) +{ /* output low nibble */ + if (d&0x08) sbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7); + else cbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7); + if (d&0x04) sbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6); + else cbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6); + if (d&0x02) sbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5); + else cbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5); + if (d&0x01) sbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); + else cbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); +} +static void lcd_out_high(uint8_t d) +{ /* output high nibble */ + if (d&0x80) sbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7); + else cbi(LCD_DATA_PORT_D7,LCD_DATA_PIN_D7); + if (d&0x40) sbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6); + else cbi(LCD_DATA_PORT_D6,LCD_DATA_PIN_D6); + if (d&0x20) sbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5); + else cbi(LCD_DATA_PORT_D5,LCD_DATA_PIN_D5); + if (d&0x10) sbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); + else cbi(LCD_DATA_PORT_D4,LCD_DATA_PIN_D4); +} + +static void lcd_e_toggle(void) +/* toggle Enable Pin */ +{ + lcd_e_high(); + _delay_us(5); + lcd_e_low(); + _delay_us(1); +} + + +static void lcd_write(uint8_t data, uint8_t rs) +{ + // configure data pins as output, already done at init + //lcd_data_port_out(); + _delay_us(4); + + /* output high nibble first */ + + lcd_out_high(data); + _delay_us(4); + + if (rs) + lcd_data_mode(); /* RS=1: write data */ + else + lcd_cmd_mode(); /* RS=0: write instruction */ + lcd_e_toggle(); + + /* output low nibble */ + lcd_out_low(data); + _delay_us(4); + + if (rs) + lcd_data_mode(); /* RS=1: write data */ + else + lcd_cmd_mode(); /* RS=0: write instruction */ + + lcd_e_toggle(); +} + + +static unsigned char lcd_waitcmd(unsigned char cmdwait) +/* this function used to loop while lcd is busy and read address i + * counter however for this we need the RW line. This function + * has been changed to just delay a bit. In that case the LCD + * is only slightly slower but we do not need the RW pin. */ +{ + _delay_us(9); + /* the display needs much longer to process a command */ + if (cmdwait){ + lcd_delay_ms(2); + } + return (0); +} + + +/* +** PUBLIC FUNCTIONS +*/ + +void lcd_command(uint8_t cmd) +/* send commando to LCD */ +{ + lcd_waitcmd(0); + lcd_write(cmd, 0); + lcd_waitcmd(1); +} + + +void lcd_gotoxy(uint8_t x, uint8_t y) +/* goto position (x,y) */ +{ +#if LCD_LINES==1 + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x); +#endif +#if LCD_LINES==2 + if (y == 0) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x); + else + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x); +#endif +#if LCD_LINES==3 + if (y == 0) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x); + else if (y == 1) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x); + else if (y == 2) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE3 + x); +#endif +#if LCD_LINES==4 + if (y == 0) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x); + else if (y == 1) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x); + else if (y == 2) + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE3 + x); + else /* y==3 */ + lcd_command((1 << LCD_DDRAM) + LCD_START_LINE4 + x); +#endif + +} /* lcd_gotoxy */ + + + +void lcd_putc(char c) +/* print character at current cursor position */ +{ + lcd_waitcmd(0); + lcd_write((unsigned char)c, 1); + lcd_waitcmd(0); +} + + +void lcd_puts(const char *s) +/* print string on lcd */ +{ + while (*s) { + lcd_putc(*s); + s++; + } + +} + + +void lcd_puts_p(const prog_char *progmem_s) +/* print string from program memory on lcd */ +{ + register char c; + + while ((c = pgm_read_byte(progmem_s++))) { + lcd_putc(c); + } + +} + +void lcd_reset(void) +{ + lcd_command(LCD_FUNCTION_DEFAULT); /* function set: display lines */ + lcd_command(LCD_DISP_OFF); /* display off */ + lcd_clrscr(); /* display clear */ + lcd_command(LCD_MODE_DEFAULT); /* set entry mode */ + lcd_command(LCD_DISP_ON); /* display/on no cursor */ + lcd_waitcmd(1); +} + +void lcd_init(void) +/* initialize display and select type of cursor */ +/* dispAttr: LCD_DISP_OFF, LCD_DISP_ON, LCD_DISP_ON_CURSOR, LCD_DISP_CURSOR_BLINK */ +{ + /*------ Initialize lcd to 4 bit i/o mode -------*/ + + lcd_data_port_out(); /* all data port bits as output */ + sbi(LCD_RS_DDR, LCD_RS_PIN); /* RS pin as output */ + sbi(LCD_E_DDR, LCD_E_PIN); /* E pin as output */ + + + lcd_delay_ms(12); /* wait 12ms or more after power-on */ + + /* initial write to lcd is 8bit */ + lcd_out_high(LCD_FUNCTION_8BIT_1LINE); + lcd_e_toggle(); + lcd_delay_ms(2); /* delay, busy flag can't be checked here */ + + lcd_out_high(LCD_FUNCTION_8BIT_1LINE); + lcd_e_toggle(); + lcd_delay_ms(2); /* delay, busy flag can't be checked here */ + + lcd_out_high(LCD_FUNCTION_8BIT_1LINE); + lcd_e_toggle(); + lcd_delay_ms(2); /* delay, busy flag can't be checked here */ + + lcd_out_high(LCD_FUNCTION_4BIT_1LINE); /* set IO mode to 4bit */ + lcd_e_toggle(); + + /* from now the lcd only accepts 4 bit I/O, we can use lcd_command() */ + lcd_reset(); +} diff --git a/lcd.h b/lcd.h new file mode 100644 index 0000000..20f67cd --- /dev/null +++ b/lcd.h @@ -0,0 +1,103 @@ +/* vim: set sw=8 ts=8 si : */ +/**************************************************************************** +* Title : HD44780 LCD library +* Authors: +* Based on Volker Oth's lcd library (http://members.xoom.com/volkeroth) +* modified by Peter Fleury's (http://jump.to/fleury). Flexible pin +* configuration by Markus Ermert. Adapted for the tuxgraphics LCD display +* by Guido Socher. +* +* Software: AVR-GCC +* Target: any AVR device +* Copyright: GPL V2 +* +*****************************************************************************/ +#ifndef LCD_H +#define LCD_H +#include + +/* you shouldn't need to change anything below this line */ + +/* instruction register bit positions */ +#define LCD_CLR 0 /* DB0: clear display */ +#define LCD_HOME 1 /* DB1: return to home position */ +#define LCD_ENTRY_MODE 2 /* DB2: set entry mode */ +#define LCD_ENTRY_INC 1 /* DB1: 1=increment, 0=decrement */ +#define LCD_ENTRY_SHIFT 0 /* DB2: 1=display shift on */ +#define LCD_ON 3 /* DB3: turn lcd/cursor on */ +#define LCD_ON_DISPLAY 2 /* DB2: turn display on */ +#define LCD_ON_CURSOR 1 /* DB1: turn cursor on */ +#define LCD_ON_BLINK 0 /* DB0: blinking cursor ? */ +#define LCD_MOVE 4 /* DB4: move cursor/display */ +#define LCD_MOVE_DISP 3 /* DB3: move display (0-> cursor) ? */ +#define LCD_MOVE_RIGHT 2 /* DB2: move right (0-> left) ? */ +#define LCD_FUNCTION 5 /* DB5: function set */ +#define LCD_FUNCTION_8BIT 4 /* DB4: set 8BIT mode (0->4BIT mode) */ +#define LCD_FUNCTION_2LINES 3 /* DB3: two lines (0->one line) */ +#define LCD_FUNCTION_10DOTS 2 /* DB2: 5x10 font (0->5x7 font) */ +#define LCD_CGRAM 6 /* DB6: set CG RAM address */ +#define LCD_DDRAM 7 /* DB7: set DD RAM address */ +#define LCD_BUSY 7 /* DB7: LCD is busy */ + +/* set entry mode: display shift on/off, dec/inc cursor move direction */ +#define LCD_ENTRY_DEC 0x04 /* display shift off, dec cursor move dir */ +#define LCD_ENTRY_DEC_SHIFT 0x05 /* display shift on, dec cursor move dir */ +#define LCD_ENTRY_INC_ 0x06 /* display shift off, inc cursor move dir */ +#define LCD_ENTRY_INC_SHIFT 0x07 /* display shift on, inc cursor move dir */ + +/* display on/off, cursor on/off, blinking char at cursor position */ +#define LCD_DISP_OFF 0x08 /* display off */ +#define LCD_DISP_ON 0x0C /* display on, cursor off */ +#define LCD_DISP_ON_BLINK 0x0D /* display on, cursor off, blink char */ +#define LCD_DISP_ON_CURSOR 0x0E /* display on, cursor on */ +#define LCD_DISP_ON_CURSOR_BLINK 0x0F /* display on, cursor on, blink char */ + +/* move cursor/shift display */ +#define LCD_MOVE_CURSOR_LEFT 0x10 /* move cursor left (decrement) */ +#define LCD_MOVE_CURSOR_RIGHT 0x14 /* move cursor right (increment) */ +#define LCD_MOVE_DISP_LEFT 0x18 /* shift display left */ +#define LCD_MOVE_DISP_RIGHT 0x1C /* shift display right */ + +/* function set: set interface data length and number of display lines */ +#define LCD_FUNCTION_4BIT_1LINE 0x20 /* 4-bit interface, single line, 5x7 dots */ +#define LCD_FUNCTION_4BIT_2LINES 0x28 /* 4-bit interface, dual line, 5x7 dots */ +#define LCD_FUNCTION_8BIT_1LINE 0x30 /* 8-bit interface, single line, 5x7 dots */ +#define LCD_FUNCTION_8BIT_2LINES 0x38 /* 8-bit interface, dual line, 5x7 dots */ + +#define LCD_MODE_DEFAULT ((1< +#include +#include +#define F_CPU 8000000UL // 8 MHz +#include +#include +#include +#include +#include "lcd.h" +#include "dac.h" +#include "kbd.h" +#include "analog.h" +#include "hardware_settings.h" + +#define SWVERSION "ver: ddcp-0.6.0" +//#define DEBUGDISP 1 + +//debug LED: +// set output to VCC, red LED off +#define LEDOFF PORTD|=(1< +{ + while(ms){ + _delay_ms(0.96); + ms--; + } +} + +// Convert an integer which is representing a float into a string. +// decimalpoint_pos sets the decimal point after 2 pos from the right: e.g 74 becomes "0.74" +// The integer may not be larger than 999. +// The integer must be a positive number. +// spacepadd can be used to add a leading speace if number is less than 10 +//static void int_to_ascii(uint16_t inum,char *outbuf,int8_t decimalpoint_pos){ +// char chbuf[8]; +// itoa(inum,chbuf,10); // convert integer to string +static void int_to_ascii(int16_t inum,char *outbuf,int8_t decimalpoint_pos,uint8_t spacepadd){ + int8_t i,j; + char chbuf[8]; + j=0; + while(inum>9 && j<7){ + // zero is ascii 48: + chbuf[j]=(char)48+ inum-((inum/10)*10); + inum=inum/10; + j++; + if(decimalpoint_pos==j){ + chbuf[j]='.'; + j++; + } + } + chbuf[j]=(char)48+inum; // most significant digit + decimalpoint_pos--; + while(j (decimalpoint_pos+2)){ + // no leading space padding needed + spacepadd=0; + } + if(decimalpoint_pos==j){ + j++; + chbuf[j]='.'; + j++; + chbuf[j]='0'; // leading zero + } + if (spacepadd){ + j++; + chbuf[j]=' '; // leading space padding: "9.50" becomes " 9.50" + } + // now reverse the order + i=0; + while(j>=0){ + outbuf[i]=chbuf[j]; + j--; + i++; + } + outbuf[i]='\0'; +} + +// convert voltage values to adc values, disp=10 is 1.0V +// ADC for voltage is 11bit: +static int16_t disp_u_to_adc(int16_t disp){ + return((int16_t)(((float)disp * 204.7) / (ADC_REF * U_DIVIDER ))); +} +// calculate the needed adc offset for voltage drop on the +// current measurement shunt (the shunt has about 0.75 Ohm =1/1.33 Ohm) +// use 1/1.2 instead of 1/1.3 because cables and connectors have as well +// a loss. +static int16_t disp_i_to_u_adc_offset(int16_t disp){ + return(disp_u_to_adc(disp/12)); +} +// convert adc values to voltage values, disp=10 is 1.0V +// disp_i_val is needed to calculate the offset for the voltage drop over +// the current measurement shunt, voltage measurement is 11bit +static int16_t adc_u_to_disp(int16_t adcunits,int16_t disp_i_val){ + int16_t adcdrop; + adcdrop=disp_i_to_u_adc_offset(disp_i_val); + if (adcunits < adcdrop){ + return(0); + } + adcunits=adcunits-adcdrop; + return((int16_t)((((float)adcunits /204.7)* ADC_REF * U_DIVIDER)+0.5)); +} +// convert adc values to current values, disp=10 needed to be printed +// by the printing function as 0.10 A, current measurement is 10bit +static int16_t disp_i_to_adc(int16_t disp){ + return((int16_t) (((disp * 10.23)* I_RESISTOR) / ADC_REF)); +} +// convert adc values to current values, disp=10 needed to be printed +// by the printing function as 0.10 A, current measurement is 10bit +static int16_t adc_i_to_disp(int16_t adcunits){ + return((int16_t) (((float)adcunits* ADC_REF)/(10.23 * I_RESISTOR)+0.5)); +} + +static void store_permanent(void){ + int16_t tmp; + uint8_t changeflag=1; + lcd_clrscr(); + if (eeprom_read_byte((uint8_t *)0x0) == 19){ + changeflag=0; + // ok magic number matches accept values + tmp=eeprom_read_word((uint16_t *)0x04); + if (tmp != set_val[1]){ + changeflag=1; + } + tmp=eeprom_read_word((uint16_t *)0x02); + if (tmp != set_val[0]){ + changeflag=1; + } + } + if (changeflag){ + lcd_puts_P("setting stored"); + eeprom_write_byte((uint8_t *)0x0,19); // magic number + eeprom_write_word((uint16_t *)0x02,set_val[0]); + eeprom_write_word((uint16_t *)0x04,set_val[1]); + }else{ + if (bpress> 3){ + // display software version after long press + lcd_puts_P(SWVERSION); + lcd_gotoxy(0,1); + lcd_puts_P("tuxgraphics.org"); + }else{ + lcd_puts_P("already stored"); + } + } + delay_ms(200); +} + +// check the keyboard +static uint8_t check_buttons(void){ + if (check_u_button(&(set_val[1]))){ + if(set_val[1]>U_MAX){ + set_val[1]=U_MAX; + } + return(1); + } + if (check_i_button(&(set_val[0]))){ + if(set_val[0]>I_MAX){ + set_val[0]=I_MAX; + } + return(1); + } + if (check_store_button()){ + store_permanent(); + return(2); + }; + return(0); +} + +int main(void) +{ + char out_buf[21]; + uint8_t i=0; + uint8_t ilimit=0; + // debug led + DDRD|= (1< 10){ + // somebody pressed permanetly the button=>scroll fast + delay_ms(30); + }else{ + bpress++; + delay_ms(180); + } + } + } + return(0); +} + diff --git a/test_dac.c b/test_dac.c new file mode 100644 index 0000000..2ecc682 --- /dev/null +++ b/test_dac.c @@ -0,0 +1,128 @@ +/********************************************* +* vim: set sw=8 ts=8 si : +* Author: Guido Socher, Copyright: GPL +* This is a test program which will write "LCD works" +* on the LCD display. +* This program is also used to test the keypad. It +* displays the button last pressed. +* +* See http://www.tuxgraphics.org/electronics/ +* +* Chip type : ATMEGA8 +* Clock frequency : Internal clock 8 Mhz +*********************************************/ +#include +#include +#define F_CPU 8000000UL // 8 MHz +#include +#include "dac.h" +#include "lcd.h" +#include "kbd.h" +#include +#include + +//debug LED: +// set output to VCC, red LED off +#define LEDOFF PORTD|=(1< */ +{ + // we use a calibrated macro. This is more + // accurate and not so much compiler dependent + // as self made code. + while(ms){ + _delay_ms(0.96); + ms--; + } +} + + +int main(void) +{ + int16_t dac_val=511; + int16_t cnt; + int8_t dac_dir=0; + char out_buf[21]; + DDRD|= (1<0xFFF){ + dac_val=0xFFF; //max, 13bit + } + if (dac_val<0){ + dac_val=0; + } + lcd_gotoxy(0,1); + itoa(dac_val,out_buf,10); + lcd_puts(out_buf); + lcd_puts(" "); + dac(dac_val); + cnt=1; + check_u_button(&cnt); + if (cnt>1){ + lcd_clrscr(); + // u+ pressed + lcd_puts_P("up"); + dac_dir=1; + LEDOFF; + } + if (cnt<1){ + lcd_clrscr(); + // u- pressed + lcd_puts_P("down"); + dac_dir=-1; + LEDON; + } + if (check_store_button()){ + lcd_clrscr(); + lcd_puts_P("pause"); + dac_dir=0; + } + delay_ms(100); + cnt=1; + check_u_button(&cnt); + if (cnt>1){ + lcd_clrscr(); + // u+ pressed + lcd_puts_P("up"); + dac_dir=1; + LEDOFF; + } + if (cnt<1){ + lcd_clrscr(); + // u- pressed + lcd_puts_P("down"); + dac_dir=-1; + LEDON; + } + if (check_store_button()){ + lcd_clrscr(); + lcd_puts_P("pause"); + dac_dir=0; + } + delay_ms(100); + } + return(0); +} diff --git a/test_lcd.c b/test_lcd.c new file mode 100644 index 0000000..0799acf --- /dev/null +++ b/test_lcd.c @@ -0,0 +1,91 @@ +/********************************************* +* vim: set sw=8 ts=8 si : +* Author: Guido Socher, Copyright: GPL +* This is a test program which will write "LCD works" +* on the LCD display. +* This program is also used to test the keypad. It +* displays the button last pressed. +* +* See http://www.tuxgraphics.org/electronics/ +* +* Chip type : ATMEGA8 +* Clock frequency : Internal clock 8 Mhz +*********************************************/ +#include +#include +#define F_CPU 8000000UL // 8 MHz +#include +#include "lcd.h" +#include "kbd.h" +#include +#include + + +void delay_ms(uint16_t ms) +/* delay for a minimum of */ +{ + // we use a calibrated macro. This is more + // accurate and not so much compiler dependent + // as self made code. + while(ms){ + _delay_ms(0.96); + ms--; + } +} + + +int main(void) +{ + int16_t cnt; + uint8_t i=0; + lcd_init(); + lcd_clrscr(); + lcd_puts("LCD works"); + init_kbd(); + delay_ms(500); + while (1) { + i++; + cnt=1; + check_u_button(&cnt); + if (cnt>1){ + lcd_clrscr(); + lcd_puts_P("U+ pressed"); + i=0; + } + if (cnt<1){ + lcd_clrscr(); + lcd_puts_P("U- pressed"); + i=0; + } + cnt=1; + check_i_button(&cnt); + if (cnt>1){ + lcd_clrscr(); + lcd_puts_P("I+ pressed"); + i=0; + } + if (cnt<1){ + lcd_clrscr(); + lcd_puts_P("I- pressed"); + i=0; + } + if (check_store_button()){ + lcd_clrscr(); + lcd_puts_P("store"); + lcd_gotoxy(0,1); + lcd_puts_P("pressed"); + i=0; + } + delay_ms(10); + if (i>150){ + lcd_clrscr(); + lcd_puts_P("press"); + lcd_gotoxy(0,1); + lcd_puts_P("a button"); + i=0; + } + + } + return(0); +} + diff --git a/winload.bat b/winload.bat new file mode 100644 index 0000000..e0aec8a --- /dev/null +++ b/winload.bat @@ -0,0 +1,10 @@ +REM *** you need to edit this file and adapt it to your WinAVR +REM *** installation. E.g replace c:\WinAVR by c:\WinAVR-20090313 +@echo -------- begin winload.bat -------- +set AVR=c:\WinAVR +set CC=avr-gcc +set PATH=c:\WinAVR\bin;c:\WinAVR\utils\bin +make -f Makefile load +@echo -------- end -------- +pause + diff --git a/winmake.bat b/winmake.bat new file mode 100644 index 0000000..b24a271 --- /dev/null +++ b/winmake.bat @@ -0,0 +1,14 @@ +REM *** you need to edit this file and adapt it to your WinAVR +REM *** installation. E.g replace c:\WinAVR by c:\WinAVR-20090313 +@echo -------- begin -------- + +set AVR=c:\WinAVR + +set CC=avr-gcc + +set PATH=c:\WinAVR\bin;c:\WinAVR\utils\bin + +make -f Makefile + +@echo -------- end -------- +pause diff --git a/winsetfuse.bat b/winsetfuse.bat new file mode 100644 index 0000000..ecf11ab --- /dev/null +++ b/winsetfuse.bat @@ -0,0 +1,8 @@ +@echo -------- begin winsetfuse.bat -------- +set AVR=c:\avrgcc +set CC=avr-gcc +set PATH=c:\avrgcc\bin;c:\avrgcc\utils\bin +make -f Makefile fuse +@echo -------- end -------- +pause + -- 2.20.1