You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

551 lines
14 KiB

  1. /***************************************************************************
  2. * Copyright (C) 11/2012 by Olaf Rempel *
  3. * razzor@kopf-tisch.de *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; version 2 of the License, *
  8. * *
  9. * This program is distributed in the hope that it will be useful, *
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  12. * GNU General Public License for more details. *
  13. * *
  14. * You should have received a copy of the GNU General Public License *
  15. * along with this program; if not, write to the *
  16. * Free Software Foundation, Inc., *
  17. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
  18. ***************************************************************************/
  19. #include <avr/io.h>
  20. #include <avr/interrupt.h>
  21. #include <avr/pgmspace.h>
  22. #include <avr/eeprom.h>
  23. #include <util/crc16.h>
  24. #define F_CPU 8000000
  25. #include <util/delay.h>
  26. #define OSCCAL_VALUE 0xA4
  27. /*
  28. * attiny26
  29. * lfuse: 0xe4 (internal 8MHz)
  30. * hfuse: 0x14 (BOD enabled)
  31. *
  32. * PA0 => current sense (0-2.56V => 0-25.6A)
  33. * PA1 => volatage sense (0-2.56V => 0-30.72V)
  34. * PA2 => mode button (low active)
  35. * PA3 => ext. 2.56V reference
  36. * PA4-7 => LCD D4-7
  37. * PB0 => MOSI (ISP)
  38. * PB1 => MISO (ISP)
  39. * PB2 => SCK (ISP)
  40. * PB3 => free
  41. * PB4 => LCD RS
  42. * PB5 => LCD RW
  43. * PB6 => LCD EN
  44. * PB7 => /RST (ISP)
  45. */
  46. #define LCD_DATA_MASK 0xF0
  47. #define LCD_RS PORTB4
  48. #define LCD_RW PORTB5
  49. #define LCD_EN PORTB6
  50. #define BUTTON PORTA2
  51. #define ADC_CURRENT 0
  52. #define ADC_VOLTAGE 1
  53. #define ADC_COMPLETE 0xFE
  54. #define ADC_IDLE 0xFF
  55. /* 8ms @8MHz / 256 */
  56. #define TIMER_TICK_RELOAD (256 - 250)
  57. /* 25 * 8ms => 200ms */
  58. #define TIMER_LCD_UPDATE 25
  59. /* 125 * 8ms => 1s */
  60. #define TIMER_SECOND 125
  61. /* autosave every minute */
  62. #define TIMER_NVRAM_SAVE 60
  63. /* bargraph 20 - 28V */
  64. #define VOLTAGE_BAR_MIN 2000
  65. #define VOLTAGE_BAR_MAX 2800
  66. /* bargraph 0 - 20A */
  67. #define CURRENT_BAR_MIN 0
  68. #define CURRENT_BAR_MAX 2000
  69. /* minimum discharge current 100mA */
  70. #define CURRENT_IDLE 10
  71. #define BUTTON_TIMER_IDLE 0xFF
  72. #define EVENT_NONE 0
  73. #define EVENT_BUTTON_PRESSED 1
  74. #define EVENT_BUTTON_RELEASED 2
  75. #define EVENT_BUTTON_TIMEOUT 3
  76. #define STATE_IDLE 0
  77. #define STATE_PRESSED 1
  78. #define STATE_WARNING 2
  79. struct _nvdata {
  80. uint8_t nvram_size; /* first */
  81. uint16_t discharge_time;
  82. uint32_t discharge_product;
  83. uint16_t nvram_crc; /* last */
  84. };
  85. static uint8_t nvram_write_pos;
  86. static struct _nvdata nvram_data;
  87. static struct _nvdata nvram_eeprom EEMEM;
  88. static struct _nvdata nvram_defaults PROGMEM = { 0 };
  89. /* create crc and store nvram data to eeprom */
  90. static void nvram_start_write(void)
  91. {
  92. uint8_t i;
  93. uint16_t crc = 0x0000;
  94. uint8_t *tmp = (uint8_t *)&nvram_data;
  95. /* write in progress? */
  96. if (EECR & (1<<EEWE))
  97. return;
  98. nvram_data.nvram_size = sizeof(struct _nvdata);
  99. for (i = 0; i < sizeof(struct _nvdata) -2; i++) {
  100. crc = _crc_ccitt_update(crc, *tmp++);
  101. }
  102. nvram_data.nvram_crc = crc;
  103. nvram_write_pos = 0;
  104. EEAR = nvram_write_pos;
  105. EEDR = ((uint8_t *)&nvram_data)[nvram_write_pos++];
  106. cli();
  107. EECR |= (1<<EEMWE);
  108. EECR |= (1<<EEWE);
  109. EECR |= (1<<EERIE);
  110. sei();
  111. }
  112. /* store nvram data to eeprom */
  113. ISR(EE_RDY_vect) {
  114. if (nvram_write_pos < sizeof(struct _nvdata)) {
  115. EEAR = nvram_write_pos;
  116. EEDR = ((uint8_t *)&nvram_data)[nvram_write_pos++];
  117. EECR |= (1<<EEMWE);
  118. EECR |= (1<<EEWE);
  119. EECR |= (1<<EERIE);
  120. } else {
  121. EECR &= ~(1<<EERIE);
  122. }
  123. }
  124. /* read nvram from eeprom and check crc */
  125. static void nvram_read(void)
  126. {
  127. uint8_t i;
  128. uint16_t crc = 0x0000;
  129. uint8_t *tmp = (uint8_t *)&nvram_data;
  130. eeprom_read_block(&nvram_data, &nvram_eeprom, sizeof(struct _nvdata));
  131. for (i = 0; i < sizeof(struct _nvdata); i++) {
  132. crc = _crc_ccitt_update(crc, *tmp++);
  133. }
  134. /* if nvram content is invalid, overwrite with defaults */
  135. if ((nvram_data.nvram_size != sizeof(struct _nvdata)) || (crc != 0x0000)) {
  136. memcpy_P(&nvram_data, &nvram_defaults, sizeof(struct _nvdata));
  137. nvram_start_write();
  138. }
  139. }
  140. static PROGMEM uint8_t bargraph[] = {
  141. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  142. 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00,
  143. 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00,
  144. 0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x1c, 0x00, 0x00,
  145. 0x00, 0x00, 0x1e, 0x1e, 0x1e, 0x1e, 0x00, 0x00,
  146. 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00,
  147. };
  148. static uint8_t lcd_port_write(uint8_t data)
  149. {
  150. uint8_t retval;
  151. /* keep button pullup */
  152. PORTA = (data & LCD_DATA_MASK) | (1<<BUTTON);
  153. asm volatile("NOP");
  154. asm volatile("NOP");
  155. PORTB |= (1<<LCD_EN);
  156. asm volatile("NOP");
  157. asm volatile("NOP");
  158. retval = (PINA & LCD_DATA_MASK);
  159. PORTB &= ~(1<<LCD_EN);
  160. return retval;
  161. }
  162. static uint8_t lcd_read(uint8_t reg)
  163. {
  164. uint8_t retval;
  165. if (reg) {
  166. PORTB |= (1<<LCD_RS);
  167. } else {
  168. PORTB &= ~(1<<LCD_RS);
  169. }
  170. PORTB |= (1<<LCD_RW);
  171. DDRA &= ~(LCD_DATA_MASK);
  172. retval = lcd_port_write(0x00);
  173. asm volatile("NOP");
  174. asm volatile("NOP");
  175. retval |= (lcd_port_write(0x00) >> 4);
  176. return retval;
  177. }
  178. static void lcd_write_no_busy_check(uint8_t reg, uint8_t data)
  179. {
  180. if (reg) {
  181. PORTB |= (1<<LCD_RS);
  182. } else {
  183. PORTB &= ~(1<<LCD_RS);
  184. }
  185. PORTB &= ~(1<<LCD_RW);
  186. DDRA |= LCD_DATA_MASK;
  187. lcd_port_write((data & 0xF0));
  188. asm volatile("NOP");
  189. asm volatile("NOP");
  190. lcd_port_write(((data & 0x0F) << 4));
  191. }
  192. static void lcd_write(uint8_t reg, uint8_t data)
  193. {
  194. lcd_write_no_busy_check(reg, data);
  195. while ((lcd_read(0x00) & 0x80));
  196. }
  197. static void lcd_print_dec2(uint8_t value)
  198. {
  199. lcd_write(0x01, '0' + (value / 10));
  200. lcd_write(0x01, '0' + (value % 10));
  201. }
  202. static void lcd_print_dec2p2(uint16_t value)
  203. {
  204. lcd_print_dec2(value / 100);
  205. lcd_write(0x01, '.');
  206. lcd_print_dec2(value % 100);
  207. }
  208. static void lcd_print_dec2p3(uint16_t value)
  209. {
  210. lcd_print_dec2(value / 1000);
  211. lcd_write(0x01, '.');
  212. value = value % 1000;
  213. lcd_write(0x01, '0' + (value / 100));
  214. lcd_print_dec2(value % 100);
  215. }
  216. static void lcd_print_time(uint16_t time)
  217. {
  218. lcd_print_dec2(time / 3600);
  219. lcd_write(0x01, ':');
  220. time = time % 3600;
  221. lcd_print_dec2(time / 60);
  222. lcd_write(0x01, ':');
  223. lcd_print_dec2(time % 60);
  224. }
  225. static void lcd_bargraph(uint8_t len, uint16_t min, uint16_t max, uint16_t value)
  226. {
  227. if (value < min)
  228. value = min;
  229. else if (value > max)
  230. value = max;
  231. /* scale to bargraph length */
  232. value = ((value - min) * (len * 5)) / (max - min) ;
  233. while (len--) {
  234. if (value >= 5) {
  235. lcd_write(0x01, 0x05);
  236. value -= 5;
  237. } else {
  238. lcd_write(0x01, value);
  239. value = 0;
  240. }
  241. }
  242. }
  243. static void lcd_write_stringP(const char *ptr)
  244. {
  245. while (1) {
  246. uint8_t data = pgm_read_byte_near(ptr++);
  247. if (data == 0x00)
  248. break;
  249. lcd_write(0x01, data);
  250. }
  251. }
  252. static volatile uint8_t adc_state;
  253. static volatile uint16_t adc_value[2];
  254. static void adc_start(uint8_t channel)
  255. {
  256. adc_state = channel;
  257. /* ext. 2.56V ref, start, irq enable, F_CPU/64 -> ~225us conversion time */
  258. ADMUX = (1<<REFS0) | channel;
  259. ADCSR = (1<<ADEN) | (1<<ADSC) | (1<<ADIE) | (1<<ADIF) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
  260. }
  261. ISR(ADC_vect)
  262. {
  263. uint8_t channel = adc_state;
  264. adc_value[channel] = ADCW;
  265. /* get next channel */
  266. if (channel == ADC_VOLTAGE) {
  267. adc_start(ADC_CURRENT);
  268. } else if (channel == ADC_CURRENT) {
  269. adc_state = ADC_COMPLETE;
  270. }
  271. }
  272. static volatile uint8_t timer_tick;
  273. ISR(TIMER0_OVF0_vect)
  274. {
  275. TCNT0 = TIMER_TICK_RELOAD;
  276. /* start sampling both channels */
  277. adc_start(ADC_VOLTAGE);
  278. timer_tick = 1;
  279. }
  280. int main(void) __attribute__ ((noreturn));
  281. int main(void)
  282. {
  283. /* calibrate to 8Mhz */
  284. OSCCAL = OSCCAL_VALUE;
  285. DDRA = LCD_DATA_MASK;
  286. DDRB = (1<<LCD_RS) | (1<<LCD_RW) | (1<<LCD_EN) | (1<<PORTB3);
  287. /* pullup for mode button */
  288. PORTA = (1<<BUTTON);
  289. /* F_CPU/256, overflow irq */
  290. TCCR0 = (1<<CS02);
  291. TIMSK = (1<<TOV0);
  292. /* execute reset (according to datasheet) */
  293. _delay_ms(50);
  294. lcd_write_no_busy_check(0x00, 0x30);
  295. _delay_ms(5);
  296. lcd_write_no_busy_check(0x00, 0x30);
  297. _delay_ms(1);
  298. lcd_write_no_busy_check(0x00, 0x30);
  299. /* switch to 4bit */
  300. lcd_write(0x00, 0x28); /* 4bit data bus */
  301. /* again, now with valid lower nibble */
  302. lcd_write(0x00, 0x28); /* 4bit data bus, 2 lines */
  303. lcd_write(0x00, 0x0C); /* display on, no cursor, no blinking */
  304. lcd_write(0x00, 0x06); /* increase address, no shift */
  305. lcd_write(0x00, 0x01); /* clear display */
  306. {
  307. uint8_t i;
  308. lcd_write(0x00, 0x40); /* write CGRAM address 0x00 */
  309. for (i = 0; i < sizeof(bargraph); i++) {
  310. lcd_write(0x01, pgm_read_byte_near(&bargraph[i]));
  311. }
  312. }
  313. sei();
  314. nvram_read();
  315. uint8_t sec_timer = 0;
  316. uint8_t lcd_timer = 0;
  317. uint8_t button_timer = BUTTON_TIMER_IDLE;
  318. uint8_t lcd_page = 0x02;
  319. uint8_t button_prev = 0;
  320. uint8_t button_state = 0;
  321. uint8_t nvram_save = 0;
  322. uint16_t voltage = 0;
  323. uint16_t current = 0;
  324. uint8_t discharging = 0;
  325. uint16_t discharge_time = nvram_data.discharge_time;
  326. uint32_t discharge_product = nvram_data.discharge_product;
  327. while (1) {
  328. if (timer_tick) {
  329. timer_tick = 0;
  330. lcd_timer++;
  331. if (discharging) {
  332. sec_timer++;
  333. if (sec_timer == TIMER_SECOND) {
  334. sec_timer = 0;
  335. discharge_time++;
  336. /* autosave during discharge */
  337. if ((discharge_time % TIMER_NVRAM_SAVE) == 0) {
  338. nvram_save = 1;
  339. }
  340. }
  341. }
  342. uint8_t button_event = EVENT_NONE;
  343. uint8_t button = PINA & (1<<BUTTON);
  344. if (!button && button_prev) {
  345. button_event = EVENT_BUTTON_PRESSED;
  346. } else if (button && !button_prev) {
  347. button_event = EVENT_BUTTON_RELEASED;
  348. } else {
  349. if (button_timer == 0) {
  350. button_event = EVENT_BUTTON_TIMEOUT;
  351. button_timer = BUTTON_TIMER_IDLE;
  352. } else if (button_timer != BUTTON_TIMER_IDLE) {
  353. button_timer--;
  354. }
  355. }
  356. button_prev = button;
  357. if ((button_state == STATE_IDLE) && (button_event == EVENT_BUTTON_PRESSED)) {
  358. lcd_write(0x00, 0x01); /* clear display */
  359. lcd_timer = TIMER_LCD_UPDATE;
  360. lcd_page ^= 0x01;
  361. button_state = STATE_PRESSED;
  362. button_timer = 250; /* 2s timeout */
  363. } else if ((button_event == EVENT_BUTTON_TIMEOUT) && (button_state == STATE_PRESSED)) {
  364. lcd_write(0x00, 0x01); /* clear display */
  365. lcd_write(0x00, 0x80 | 1);
  366. lcd_write_stringP(PSTR("reset counter?"));
  367. lcd_page = 0;
  368. button_state = STATE_WARNING;
  369. button_timer = 250; /* another 2s timeout */
  370. } else if (((button_event == EVENT_BUTTON_TIMEOUT) && (button_state == STATE_WARNING)) ||
  371. (button_event == EVENT_BUTTON_RELEASED)) {
  372. if (button_event == EVENT_BUTTON_TIMEOUT) {
  373. discharge_time = 0;
  374. discharge_product = 0;
  375. nvram_save = 1;
  376. }
  377. if (button_state == STATE_WARNING) {
  378. lcd_write(0x00, 0x01); /* clear display */
  379. lcd_timer = TIMER_LCD_UPDATE;
  380. lcd_page = 2;
  381. }
  382. button_state = STATE_IDLE;
  383. button_timer = BUTTON_TIMER_IDLE;
  384. }
  385. }
  386. if (adc_state == ADC_COMPLETE) {
  387. adc_state = ADC_IDLE;
  388. /* voltage in 10mV increments */
  389. voltage = adc_value[ADC_VOLTAGE] * 3;
  390. /* current in 10mA increments */
  391. current = (adc_value[ADC_CURRENT] * 5) >> 1;
  392. if (discharging) {
  393. discharge_product += current;
  394. }
  395. if (current >= CURRENT_IDLE) {
  396. discharging = 1;
  397. } else if (sec_timer == 0) {
  398. discharging = 0;
  399. }
  400. }
  401. if (lcd_timer == TIMER_LCD_UPDATE) {
  402. lcd_timer = 0;
  403. if (lcd_page) {
  404. lcd_write(0x00, 0x80 | 0);
  405. lcd_print_dec2p2(voltage);
  406. lcd_write(0x01, 'V');
  407. lcd_write(0x00, 0x80 | 40);
  408. lcd_print_dec2p2(current);
  409. lcd_write(0x01, 'A');
  410. if (lcd_page == 2) {
  411. lcd_write(0x00, 0x80 | 8);
  412. lcd_print_time(discharge_time);
  413. lcd_write(0x00, 0x80 | 48);
  414. /* 125 ticks per second, 3600s per hour, 10mA Steps */
  415. lcd_print_dec2p3(discharge_product / (TIMER_SECOND * 3600ULL / 10ULL));
  416. lcd_write(0x01, 'A');
  417. lcd_write(0x01, 'h');
  418. } else if (lcd_page == 3) {
  419. lcd_write(0x00, 0x80 | 7);
  420. lcd_bargraph(9, VOLTAGE_BAR_MIN, VOLTAGE_BAR_MAX, voltage);
  421. lcd_write(0x00, 0x80 | 47);
  422. lcd_bargraph(9, CURRENT_BAR_MIN, CURRENT_BAR_MAX, current);
  423. }
  424. }
  425. }
  426. if (nvram_save) {
  427. nvram_save = 0;
  428. nvram_data.discharge_time = discharge_time;
  429. nvram_data.discharge_product = discharge_product;
  430. nvram_start_write();
  431. }
  432. }
  433. }