commit 882541392f8df09842d15518acca1124d0552c5e Author: Stuart Longland Date: Sat Sep 3 17:31:15 2016 +1000 Initial check-in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2641cde --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.*.swp +*.swp +*.o +*.elf +*.hex diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9afaffa --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +# Solar Powered Personal Cloud Power Controller +# (C) 2016 Stuart Longland +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +-include local.mk + +CFLAGS = -Os -g -mmcu=attiny24a -Wall -Werror +LDFLAGS = -mmcu=attiny24a -Wall -Werror +CPPFLAGS = -DF_CPU=1000000UL +CROSS_COMPILE ?= avr- +CC = $(CROSS_COMPILE)gcc +OBJCOPY = $(CROSS_COMPILE)objcopy +OBJDUMP = $(CROSS_COMPILE)objdump +SIZE = $(CROSS_COMPILE)size +PROG_ARGS ?=-c stk500v2 -P /dev/ttyACM0 +PROG_DEV ?= t24 + +.PHONY: clean all + +all: powerctl.ihex + +clean: + -rm *.ihex *.elf *.o + +%.ihex: %.elf + $(OBJCOPY) -j .text -j .data -O ihex $^ $@ + +%.elf: + $(CC) $(LDFLAGS) -o $@ $^ + $(SIZE) -d $@ + +powerctl.elf: powerctl.o uart.o +powerctl.o: board.h +uart.o: uartcfg.h uart.h + +%.pgm: %.ihex + avrdude $(PROG_ARGS) -p $(PROG_DEV) -U flash:w:$^:i diff --git a/README.md b/README.md new file mode 100644 index 0000000..042d984 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +Power controller for solar powered personal cloud +================================================= + +This firmware is intended to control the charging of a battery bank that +powers a small computer cluster running a private cloud system. The +idea is to try to keep the battery within a small range of voltages, +charging from solar or mains based chargers as necessary to top the +battery back up. + +Circuit description +------------------- + +An ATTiny24A microcontroller runs from a 5V rail derived from the +battery. It uses its internal RC oscillator running at 1MHz. + +Connected to PB0 and PB1 are the two MOSFETs that turn on the input +sources, one for solar (PB1), the other for a mains charger (PB0). + +The source pins of these connect to the battery positive input, so when +the FET is on, power may flow from that source to the battery. + +A third MOSFET connects to PB2, this is PWM-controlled to manage some +cooling fans. The internal temperature sensor is used to decide whether +the fans should be on or off, and at what speed. + +Connecting to each input, and to the battery, are separate voltage +dividers, comprising of a 1.5kOhm and 100Ohm resistors. These divide +the input voltage by 16, and the divided voltage is fed into the ADC +pins PA0 (mains), PA1 (solar) and PA2 (battery). + +LEDs connect to PA7, PA6, PA5, PA4 and PA3 to 0v. ICSP is shared with the +LEDs. + +GPIOs +----- + + PB0: Mains MOSFET (active HIGH) + PB1: Solar MOSFET (active HIGH) + PB2: Fan MOSFET (active HIGH) + PB3: nRESET + PA7: Temperature Low LED (active HIGH) + PA6: Temperature High LED (active HIGH) + ICSP MOSI + PA5: Battery Voltage High LED (active HIGH) + ICSP MISO + PA4: Warning LED (active high) + ICSP SCK + Debug Tx + PA3: Battery Voltage Good LED (active HIGH) + PA2: Analogue Input: Battery voltage + PA1: Analogue Input: Solar voltage + PA0: Analogue Input: Mains voltage + +Firmware description +-------------------- + +The firmware may be compiled in DEBUG mode by adding -DDEBUG to the +CPPFLAGS. In this mode, PA4 is used instead for a UART Tx pin at 1200 +baud, 8 bits, no parity, one stop bit. This is done in software, using +Timer 1 as a baud rate generator. + +Timer 1 also does double-duty managing timings for events. + +Timer 0 is used in PWM mode to control the fans. + +The firmware does some quick initialisation before entering the main +loop. If DEBUG is enabled, "INIT xx" will be displayed, where xx is the +value of the MCUSR register. + +Two counters are decremented by the Timer 1 overflow interrupt. +`led_timeout` causes the main loop to update the state of the LEDs when +it hits zero, and `adc_timeout` triggers a new ADC capture run when it +reaches zero. + +The three analogue inputs and temperature sensor are scanned when +`adc_timeout` reaches zero, then the state analysed. The battery +voltage is compared to the previous reading to dissern if the battery is +charging, discharging or holding steady. + +If this state has not changed, a battery state counter increments, +otherwise it is reset. + +The current state of the FETs is checked. Three states are valid: +- Idle state: all FETs off +- Mains charge: MAINS FET turned on +- Solar charge: SOLAR FET turned on + +IF statements at this point compare the battery voltage to the +thresholds, and decide whether to switch voltage or not. + +LED indications +--------------- + +The LEDs have the following meanings: + +Warning LED (PA4; DEBUG undefined): +- Off: No warning condition +- Flashing: Battery below critical threshold or temperature above + maximum threshold +- On: Not used + +When in DEBUG mode, this LED may flicker with serial activity, and will +remain ON when idle. + +Temperature LEDs (PA6, PA7): +- PA7 On, PA6 Off: Temperature below minimum threshold +- PA7 Flashing, PA6 Off: Temperature above minimum threshold +- PA7 Off, PA6 Flashing: Temperature above maximum threshold +- Other states: not used + +Battery LEDs (PA5, PA3): +- PA3 Flashing, PA5 Off: Battery below low threshold +- PA3 Off, PA5 Flashing: Battery above high threshold +- PA3 On, PA5 Off: Battery is in "good" range (between low and high) +- Other states: not used diff --git a/board.h b/board.h new file mode 100644 index 0000000..597815d --- /dev/null +++ b/board.h @@ -0,0 +1,37 @@ +#ifndef _BOARD_H +#define _BOARD_H + +/* LEDs */ +#define LED_TEMP_LOW (1 << 7) +#define LED_TEMP_HIGH (1 << 6) +#define LED_BATT_HIGH (1 << 5) +#define LED_WARNING (1 << 4) +#define LED_BATT_GOOD (1 << 3) +#define LED_PORT PORTA +#define LED_PORT_DDR_REG DDRA +#define LED_PORT_DDR_VAL ( LED_TEMP_LOW \ + | LED_TEMP_HIGH \ + | LED_BATT_HIGH \ + | LED_WARNING \ + | LED_BATT_GOOD ) + +/* MOSFETs */ +#define FET_MAINS (1 << 0) +#define FET_SOLAR (1 << 1) +#define FET_FAN (1 << 2) +#define FET_SRC_MASK (FET_MAINS|FET_SOLAR) +#define FET_PORT PORTB +#define FET_PORT_DDR_REG DDRB +#define FET_PORT_DDR_VAL ( FET_MAINS \ + | FET_SOLAR \ + | FET_FAN ) + +/* ADC channels */ +#define ADC_CH_MAINS (1 << 0) +#define ADC_CH_SOLAR (1 << 1) +#define ADC_CH_BATT (1 << 2) +#define ADC_CH_EN ( ADC_CH_MAINS \ + | ADC_CH_SOLAR \ + | ADC_CH_BATT ) + +#endif diff --git a/powerctl.c b/powerctl.c new file mode 100644 index 0000000..f94d888 --- /dev/null +++ b/powerctl.c @@ -0,0 +1,506 @@ +#include +#include +#include +#include + +#ifdef DEBUG +/*! If enabled, the warning LED doubles as UART Tx pin */ +#include "uart.h" +#endif + +#include "board.h" + +/*! ADMUX setting for selecting 1.1V reference */ +#define ADC_REF_1V1 (2 << REFS0) +/*! ADMUX setting for mains input voltage reading */ +#define ADC_MUX_MAINS (ADC_REF_1V1 | 0x00) +/*! ADMUX setting for solar input voltage reading */ +#define ADC_MUX_SOLAR (ADC_REF_1V1 | 0x01) +/*! ADMUX setting for battery input voltage reading */ +#define ADC_MUX_BATT (ADC_REF_1V1 | 0x02) +/*! ADMUX setting for temperature reading */ +#define ADC_MUX_TEMP (ADC_REF_1V1 | 0x22) + +/*! + * For state machine, the last state of the ADC MUX so we know whether + * to ignore the sample or not. Datasheet recommends discarding samples + * to let things stabalise when switching sources/references. + */ +static volatile uint8_t last_admux = 0; + +/*! + * For state machine, determines what the battery was one sample ago so + * we know if it's charging, discharging, or remaining static. ADC units. + */ +static volatile uint16_t last_adc_batt = 0; + +/*! + * Current reading of the battery voltage in ADC units. + */ +static volatile uint16_t adc_batt = 0; + +/*! + * Current reading of the solar charger voltage in ADC units. + */ +static volatile uint16_t adc_solar = 0; + +/*! + * Current reading of the mains charger voltage in ADC units. + */ +static volatile uint16_t adc_mains = 0; + +/*! + * Current reading of the internal temperature sensor in ADC units. + */ +static volatile uint16_t adc_temp = 0; + +/*! + * How long before we next take a reading? + */ +static volatile uint16_t adc_timeout = 0; +/*! + * How many Timer1 ticks between ADC readings? + */ +#define ADC_TIMEOUT (1200) + +/*! + * State of the battery. + * -1: discharging + * 0: remaining steady + * 1: charging + */ +static volatile int8_t batt_state = 0; +/*! + * The state of the battery at last check. + */ +static volatile int8_t last_batt_state = 0; +/*! + * The number of readings that the battery has maintained this state. + */ +static volatile uint8_t batt_state_counter = 0; +/*! + * How long before we can consider switching sources. + */ +static volatile uint8_t src_timeout = 0; +#define SRC_TIMEOUT (15) /*!< How long to wait before switching */ + +/*! + * How long before we change LED states? + */ +static volatile uint8_t led_timeout = 0; +#define LED_TIMEOUT (150) + +/* + * Temperature ranges and fan PWM settings + */ +#define TEMP_MIN (275 << 6) /*!< ~20°C, approx ADC reading */ +#define TEMP_MAX (295 << 6) /*!< ~30°C, approx ADC reading */ +#define FAN_PWM_MIN (128) /*!< Minimum PWM value */ +#define FAN_PWM_MAX (255) /*!< Maximum PWM value */ + +/* + * ADC Voltage divider settings + */ +#define VDIV_R1 (1500ULL) /*!< R1 = 1.5kOhm */ +#define VDIV_R2 (100ULL) /*!< R2 = 100 Ohm */ +/* + * ADC settings + */ +#define ADC_REF (1100ULL) /*!< AREF = 1.1mV */ +#define ADC_MAX (65535ULL) /*!< ADLAR = 1 */ + +/*! + * Macro for computing ADC measurements. This assumes the input to the + * ADC pin is via a voltage divider made up of resistors R1 and R2, with + * the input voltage applied across both resistors and the ADC measuring + * across R2. + * + * @param mv Voltage in millivolts + * @returns Approximate ADC reading + */ +# define ADC_READ(mv) ( \ + (ADC_MAX * ((uint64_t)(mv)) * VDIV_R2) \ + / \ + (ADC_REF * (VDIV_R1 + VDIV_R2)) \ +) + +/*! + * "Critical" battery voltage. This is considered a serious condition. + */ +#define VBATT_CRIT ADC_READ(11800) +/*! + * "Low" battery voltage. Indication that we should turn a charger on. + */ +#define VBATT_LOW ADC_READ(12000) +/*! + * "High" battery voltage. Indication we should turn the charger off. + */ +#define VBATT_HIGH ADC_READ(13500) + +/* Debug messages */ +#ifdef DEBUG +const char STR_INIT[] PROGMEM = {"INIT "}; +const char STR_ADC[] PROGMEM = {"ADC "}; +const char STR_START[] PROGMEM = {"START "}; +const char STR_READ[] PROGMEM = {"READ "}; +const char STR_NL[] PROGMEM = {"\r\n"}; +#endif + +#define SRC_NONE (0) /*!< Turn off all chargers */ +#define SRC_SOLAR (1) /*!< Turn on solar charger */ +#define SRC_MAINS (2) /*!< Turn on mains charger */ +/*! + * Switch between chargers. This is does a "break-before-make" switchover + * of charging sources to switch from mains to solar, solar to mains, or to + * switch from charging to discharging mode. It expressly forbids turning + * both chargers on simultaneously. + */ +void select_src(uint8_t src) { + switch(src) { + case SRC_SOLAR: + FET_PORT &= ~FET_MAINS; + FET_PORT |= FET_SOLAR; + break; + case SRC_MAINS: + FET_PORT &= ~FET_SOLAR; + FET_PORT |= FET_MAINS; + break; + case SRC_NONE: + default: + FET_PORT &= ~FET_SRC_MASK; + break; + } + src_timeout = SRC_TIMEOUT; +} + +/*! + * Main entrypoint */ +int main(void) { + /* Configure LEDs */ + LED_PORT_DDR_REG = LED_PORT_DDR_VAL; + LED_PORT = 0; + + /* Configure MOSFETs */ + FET_PORT_DDR_REG = FET_PORT_DDR_VAL; + FET_PORT = 0; + + /* Turn on ADC and timers */ + PRR &= ~((1 << PRTIM0) | (1 << PRTIM1) | (1 << PRADC)); + + /* Configure Timer0: Fan PWM */ + TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); + TCCR0B = (1 << CS00); + /* Half-speed fan until we know what to do */ + OCR0A = 128; + + /* + * Configure Timer1: 1.2kHz System tick timer + * / baud rate generator for debug output + */ + TCCR1A = 0; + TCCR1B = (1 << WGM12) | (1 << CS10); + TCCR1C = 0; + OCR1A = F_CPU/1200; + TIMSK1 = (1 << OCIE1A); + + /* ADC configuration */ + DIDR0 = ADC_CH_EN; + ADMUX = ADC_MUX_TEMP; + ADCSRB = (1 << ADLAR); + ADCSRA = (1 << ADIE) + | (1 << ADPS2) + | (1 << ADPS1) + | (1 << ADPS0); + + /* Configure UART */ + sei(); +#ifdef DEBUG + uart_init(); + uart_tx_str(STR_INIT); + uart_tx_hex_byte(MCUSR); + uart_tx_str(STR_NL); +#endif + MCUSR = 0; + while(1) { + if (!led_timeout) { +#ifndef DEBUG + if ((adc_batt < VBATT_CRIT) + || (adc_temp > TEMP_MAX)) { + /* Warning conditions */ + LED_PORT ^= LED_WARNING; + } else { + LED_PORT &= ~LED_WARNING; + } +#endif + + if (adc_batt < VBATT_LOW) { + /* Battery is low */ + LED_PORT &= ~LED_BATT_HIGH; + LED_PORT ^= LED_BATT_GOOD; + } else if (adc_batt >= VBATT_HIGH) { + /* Battery is above "high" threshold */ + LED_PORT ^= LED_BATT_HIGH; + LED_PORT &= ~LED_BATT_GOOD; + } else { + /* Battery is above "low" threshold */ + LED_PORT |= LED_BATT_GOOD; + LED_PORT &= ~LED_BATT_HIGH; + } + + if (adc_temp < TEMP_MIN) { + LED_PORT |= LED_TEMP_LOW; + LED_PORT &= ~LED_TEMP_HIGH; + } else if (adc_temp < TEMP_MAX) { + LED_PORT ^= LED_TEMP_LOW; + LED_PORT &= ~LED_TEMP_HIGH; + } else { + LED_PORT &= ~LED_TEMP_LOW; + LED_PORT ^= LED_TEMP_HIGH; + } + led_timeout = LED_TIMEOUT; + } + if (!adc_timeout) { + adc_timeout = ADC_TIMEOUT; + ADCSRA |= (1 << ADEN) | (1 << ADSC); +#ifdef DEBUG + uart_tx_str(STR_ADC); + uart_tx_str(STR_READ); +#endif + while(ADCSRA & (1 << ADEN)); +#ifdef DEBUG + uart_tx_str(STR_NL); + uart_tx_str(STR_ADC); + uart_tx('T'); uart_tx_hex_word(adc_temp); + uart_tx(' '); + uart_tx('B'); uart_tx_hex_word(adc_batt); + uart_tx(' '); + uart_tx('L'); uart_tx_hex_word(last_adc_batt); + uart_tx(' '); + uart_tx('d'); + if (last_adc_batt > adc_batt) { + uart_tx('-'); + uart_tx_hex_word(last_adc_batt - adc_batt); + } else { + uart_tx('+'); + uart_tx_hex_word(adc_batt - last_adc_batt); + } + uart_tx(' '); + uart_tx('S'); uart_tx_hex_word(adc_solar); + uart_tx(' '); + uart_tx('M'); uart_tx_hex_word(adc_mains); + uart_tx(' '); + uart_tx('F'); uart_tx_hex_byte(FET_PORT); +#endif + + /* Battery direction */ +#ifdef DEBUG + uart_tx(' '); + uart_tx('b'); +#endif + if ((!last_adc_batt) || (adc_batt == last_adc_batt)) { + batt_state = 0; /* Steady? */ +#ifdef DEBUG + uart_tx('='); +#endif + } else if (adc_batt > last_adc_batt) { + batt_state = 1; +#ifdef DEBUG + uart_tx('+'); +#endif + } else if (adc_batt < last_adc_batt) { + batt_state = -1; +#ifdef DEBUG + uart_tx('-'); +#endif + } + + if (last_batt_state == batt_state) { + batt_state_counter++; + } else { + batt_state_counter = 0; + last_batt_state = batt_state; + } + + /* Battery control */ + uint8_t state = FET_PORT & FET_SRC_MASK; + switch (state) { + case 0: + /* Idle state */ +#ifdef DEBUG + uart_tx('I'); +#endif + if ((adc_batt < VBATT_CRIT) + && (adc_mains > adc_batt)) { + /* Charger urgently needed. */ +#ifdef DEBUG + uart_tx('C'); + uart_tx('M'); +#endif + select_src(SRC_MAINS); + } else if (adc_batt < VBATT_LOW) { + /* Charger needed. */ +#ifdef DEBUG + uart_tx('L'); +#endif + if ((adc_solar >= adc_mains) + && (adc_solar > adc_batt)) { +#ifdef DEBUG + uart_tx('S'); +#endif + select_src(SRC_SOLAR); + } else { +#ifdef DEBUG + uart_tx('M'); +#endif + select_src(SRC_MAINS); + } + } + break; + case FET_SOLAR: +#ifdef DEBUG + uart_tx('S'); +#endif + /* Are we over voltage? */ + if (adc_batt >= VBATT_HIGH) { +#ifdef DEBUG + uart_tx('H'); +#endif + select_src(SRC_NONE); + } else if ((adc_batt < VBATT_CRIT) + && (adc_mains > adc_solar) + && (adc_mains > adc_batt)) { +#ifdef DEBUG + uart_tx('C'); +#endif + select_src(SRC_MAINS); + /* Are we still discharging? */ + } else if ((!src_timeout) + && (adc_mains > adc_batt) + && (batt_state <= 0) + && (batt_state_counter > 10)) { +#ifdef DEBUG + uart_tx('M'); +#endif + select_src(SRC_MAINS); + } else if (src_timeout) { +#ifdef DEBUG + uart_tx('s'); + uart_tx_hex_byte(batt_state_counter); + uart_tx('t'); + uart_tx_hex_byte(src_timeout); +#endif + src_timeout--; + } + break; + case FET_MAINS: +#ifdef DEBUG + uart_tx('M'); +#endif + /* Are we over voltage? */ + if (adc_batt >= VBATT_HIGH) { +#ifdef DEBUG + uart_tx('H'); +#endif + select_src(SRC_NONE); + /* Are we still critical? */ + } else if (adc_batt < VBATT_CRIT) { +#ifdef DEBUG + uart_tx('C'); +#endif + if (adc_mains < adc_solar) { + /* Mains no good, try solar */ +#ifdef DEBUG + uart_tx('S'); +#endif + select_src(SRC_SOLAR); + } + /* Is solar better now? */ + } else if ((!src_timeout) + && (adc_solar > adc_mains)) { +#ifdef DEBUG + uart_tx('S'); +#endif + select_src(SRC_SOLAR); + } else if (src_timeout) { +#ifdef DEBUG + uart_tx('t'); + uart_tx_hex_byte(src_timeout); +#endif + src_timeout--; + } + break; + default: + /* Should not get here */ +#ifdef DEBUG + uart_tx('!'); +#endif + select_src(SRC_NONE); + } + + /* Fan control */ + if (adc_temp > TEMP_MAX) { + /* We're at the maximum temperature, FULL SPEED! */ + OCR0A = FAN_PWM_MAX; + } else if (adc_temp > TEMP_MIN) { + /* Scale fan speed linearly with temperature */ + OCR0A = (((adc_temp - TEMP_MIN) + * (FAN_PWM_MIN - FAN_PWM_MAX)) + / (TEMP_MAX - TEMP_MIN)) + + FAN_PWM_MIN; + } else { + /* Turn fans off completely. */ + OCR0A = 0; + } +#ifdef DEBUG + uart_tx(' '); + uart_tx('f'); + uart_tx_hex_byte(OCR0A); + + uart_tx_str(STR_NL); +#endif + } + } + return 0; +} + +ISR(TIM1_COMPA_vect) { +#ifdef DEBUG + uart_tick(); +#endif + if (adc_timeout) + adc_timeout--; + if (led_timeout) + led_timeout--; +} + +ISR(ADC_vect) { + uint16_t adc = ADCW; + if (last_admux == ADMUX) { + switch(last_admux) { + case ADC_MUX_TEMP: + adc_temp = adc; + ADMUX = ADC_MUX_BATT; + ADCSRA |= (1 << ADSC); + break; + case ADC_MUX_BATT: + last_adc_batt = adc_batt; + adc_batt = adc; + ADMUX = ADC_MUX_SOLAR; + ADCSRA |= (1 << ADSC); + break; + case ADC_MUX_SOLAR: + adc_solar = adc; + ADMUX = ADC_MUX_MAINS; + ADCSRA |= (1 << ADSC); + break; + case ADC_MUX_MAINS: + adc_mains = adc; + default: + ADMUX = ADC_MUX_TEMP; + ADCSRA &= ~(1 << ADEN); + } + } else { + ADCSRA |= (1 << ADSC); + last_admux = ADMUX; + } +} diff --git a/uart.c b/uart.c new file mode 100644 index 0000000..ecb6d4e --- /dev/null +++ b/uart.c @@ -0,0 +1,116 @@ +/*! + * Very simple software UART for AVR microcontrollers + * (C) 2016 Stuart Longland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "uart.h" +#include "uartcfg.h" + +#include +#include +#include +#include + +#define TX_BIT (1 << UART_TX_BIT) + +#define BIT_US (1000000/UART_BAUD) /*!< Bit period in microseconds */ + +static volatile uint8_t uart_ticks = 0; + +/*! + * Configure the UART pins. + */ +void uart_init() { + UART_TX_DDR |= TX_BIT; + UART_TX_PORT |= TX_BIT; +} + +static void uart_wait(uint8_t ticks) { + uart_ticks = ticks; + while(uart_ticks); +} + +void uart_tick() { + if (uart_ticks) + uart_ticks--; +} + +/*! + * Send a raw byte to the UART. + */ +void uart_tx(uint8_t byte) { + uint8_t mask = 1; + /* Start bit */ + UART_TX_PORT &= ~TX_BIT; uart_wait(1); + while(mask) { + if (mask & byte) + UART_TX_PORT |= TX_BIT; + else + UART_TX_PORT &= ~TX_BIT; + uart_wait(1); + mask <<= 1; + } + /* Stop bit */ + UART_TX_PORT |= TX_BIT; uart_wait(1); +} + +/*! + * Send a text string (in pgmspace) to the UART. + * String shall be null-terminated! + */ +void uart_tx_str(const char* str) { + char c = pgm_read_byte(str); + while (c) { + str++; + uart_tx(c); + c = pgm_read_byte(str); + } +} + +/*! + * Send a 4-bit nybble to the UART. + */ +void uart_tx_hex(uint8_t nybble) { + if (nybble >= 10) + uart_tx(nybble - 10 + 'a'); + else + uart_tx(nybble + '0'); +} + +/*! + * Send a 8-bit byte to the UART. + */ +void uart_tx_hex_byte(uint8_t byte) { + uart_tx_hex(byte >> 4); + uart_tx_hex(byte & 0x0f); +} + +/*! + * Send a 16-bit word to the UART. + */ +void uart_tx_hex_word(uint16_t word) { + uart_tx_hex_byte(word >> 8); + uart_tx_hex_byte(word & 0xff); +} + +/*! + * Send a 32-bit long word to the UART. + */ +void uart_tx_hex_lword(uint32_t lword) { + uart_tx_hex_word(lword >> 16); + uart_tx_hex_word(lword & 0xffff); +} diff --git a/uart.h b/uart.h new file mode 100644 index 0000000..d4c833a --- /dev/null +++ b/uart.h @@ -0,0 +1,60 @@ +#ifndef _UART_H +#define _UART_H + +/*! + * Very simple software UART for AVR microcontrollers + * (C) 2016 Stuart Longland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +/*! + * Configure the UART pins. + */ +void uart_init(); + +/*! + * UART tick timer + */ +void uart_tick(); + +/*! + * Send a raw byte to the UART. + */ +void uart_tx(uint8_t byte); + +/*! + * Send a text string (in pgmspace) to the UART. + * String shall be null-terminated! + */ +void uart_tx_str(const char* str); + +/*! + * Send a 8-bit byte to the UART. + */ +void uart_tx_hex_byte(uint8_t byte); + +/*! + * Send a 16-bit word to the UART. + */ +void uart_tx_hex_word(uint16_t word); + +/*! + * Send a 32-bit long word to the UART. + */ +void uart_tx_hex_lword(uint32_t lword); +#endif diff --git a/uartcfg.h b/uartcfg.h new file mode 100644 index 0000000..b566cba --- /dev/null +++ b/uartcfg.h @@ -0,0 +1,37 @@ +#ifndef _UARTCFG_H +#define _UARTCFG_H +/*! + * Very simple software UART for AVR microcontrollers + * (C) 2016 Stuart Longland + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* The following defines the configuration for the UART driver. */ + +/*! + * UART BAUD rate. This is the speed that we transmit at. For the 1MHz + * RC clock, 4800 is the maximum speed (out of the "standard" ones) that + * will work. If you're running at 8MHz or with an external crystal, you + * may go higher. + */ +#define UART_BAUD (300) + +/* Port settings */ +#define UART_TX_PORT PORTA /*!< GPIO port register for Tx */ +#define UART_TX_DDR DDRA /*!< GPIO direction register for Tx */ +#define UART_TX_BIT 4 /*!< GPIO pin for Tx */ + +#endif