From eec2e5aae40d0831c385b6f8ff91fac466b4bb7b Mon Sep 17 00:00:00 2001 From: ga Date: Thu, 21 Jan 2021 15:30:19 +0000 Subject: [PATCH] Implement the BIN and IPR bits for attinyX5 and change the ADC sample timing to roughly match the datasheet. Add a test. Those MCUs have no UART, so test.c and test.h have changes to use an alternative method. --- simavr/cores/sim_tinyx5.h | 2 +- simavr/sim/avr_adc.c | 161 ++++++++++++++++++++++----------- simavr/sim/avr_adc.h | 21 ++++- tests/attiny85_adc_test.c | 127 ++++++++++++++++++++++++++ tests/test_attiny85_adc_test.c | 101 +++++++++++++++++++++ tests/tests.c | 72 ++++++++++++--- tests/tests.h | 8 ++ 7 files changed, 419 insertions(+), 73 deletions(-) create mode 100644 tests/attiny85_adc_test.c create mode 100644 tests/test_attiny85_adc_test.c diff --git a/simavr/cores/sim_tinyx5.h b/simavr/cores/sim_tinyx5.h index ca7ef25..52f4bc7 100644 --- a/simavr/cores/sim_tinyx5.h +++ b/simavr/cores/sim_tinyx5.h @@ -135,7 +135,7 @@ const struct mcu_t SIM_CORENAME = { }, .bin = AVR_IO_REGBIT(ADCSRB, BIN), - .ipr = AVR_IO_REGBIT(ADCSRA, IPR), + .ipr = AVR_IO_REGBIT(ADCSRB, IPR), .muxmode = { [0] = AVR_ADC_SINGLE(0), [1] = AVR_ADC_SINGLE(1), diff --git a/simavr/sim/avr_adc.c b/simavr/sim/avr_adc.c index 51ab1ca..611cdd5 100644 --- a/simavr/sim/avr_adc.c +++ b/simavr/sim/avr_adc.c @@ -34,30 +34,38 @@ avr_adc_int_raise( // if the interrupts are not used, still raised the UDRE and TXC flag avr_raise_interrupt(avr, &p->adc); avr_regbit_clear(avr, p->adsc); - p->first = 0; - p->read_status = 0; if( p->adts_mode == avr_adts_free_running ) avr_raise_irq(p->io.irq + ADC_IRQ_IN_TRIGGER, 1); + if (!p->read_status) { + /* Update I/O registers. */ + + avr->data[p->r_adcl] = p->result & 0xff; + avr->data[p->r_adch] = p->result >> 8; + } } return 0; } -static uint8_t -avr_adc_read_l( - struct avr_t * avr, avr_io_addr_t addr, void * param) +static avr_cycle_count_t +avr_adc_convert(struct avr_t * avr, avr_cycle_count_t when, void * param) { - avr_adc_t * p = (avr_adc_t *)param; - if (p->read_status) // conversion already done - return avr_core_watch_read(avr, addr); + avr_adc_t *p = (avr_adc_t *)param; + + p->first = 0; // Converter initialised + + /* Ask the calling program for inputs. */ + + avr_adc_mux_t mux = p->muxmode[p->current_muxi]; + union { + avr_adc_mux_t mux; + uint32_t v; + } e = { .mux = mux }; + avr_raise_irq(p->io.irq + ADC_IRQ_OUT_TRIGGER, e.v); - uint8_t refi = avr_regbit_get_array(avr, p->ref, ARRAY_SIZE(p->ref)); - uint16_t ref = p->ref_values[refi]; - uint8_t muxi = avr_regbit_get_array(avr, p->mux, ARRAY_SIZE(p->mux)); - avr_adc_mux_t mux = p->muxmode[muxi]; // optional shift left/right - uint8_t shift = avr_regbit_get(avr, p->adlar) ? 6 : 0; // shift LEFT + uint8_t shift = p->current_extras.adjust ? 6 : 0; // shift LEFT - uint32_t reg = 0; + int32_t reg = 0, clipped = 0; switch (mux.kind) { case ADC_MUX_SINGLE: reg = p->adc_values[mux.src]; @@ -81,7 +89,10 @@ avr_adc_read_l( reg = avr->vcc / 4; break; } - uint32_t vref = 3300; + + int32_t vref = 3300; + uint16_t ref = p->ref_values[p->current_refi]; + switch (ref) { case ADC_VREF_VCC: if (!avr->vcc) @@ -107,18 +118,44 @@ avr_adc_read_l( // printf("ADCL %d:%3d:%3d read %4d vref %d:%d=%d\n", // mux.kind, mux.diff, mux.src, // reg, refi, ref, vref); - reg = (reg * 0x3ff) / vref; // scale to 10 bits ADC -// printf("ADC to 10 bits 0x%x %d\n", reg, reg); - if (reg > 0x3ff) { - AVR_LOG(avr, LOG_WARNING, "ADC: channel %d clipped %u/%u VREF %d\n", mux.kind, reg, 0x3ff, vref); - reg = 0x3ff; + + if (mux.kind == ADC_MUX_DIFF) { + if (p->current_extras.negate) + reg = -reg; + if (p->current_extras.bipolar) { + reg = (reg * (int32_t)0x1ff) / vref; // scale to 9 bits + if (reg > (int32_t)0x1ff) { + clipped = 0x1ff; + } else if (reg < -(int32_t)0x1ff) { + clipped = 0x200; + } + } else { + reg = (reg * (int32_t)0x3ff) / vref; // scale to 10 bit + if (reg < 0 || reg > (int32_t)0x3ff) + clipped = 0x1ff; + } + } else { + reg = (reg * (int32_t)0x3ff) / vref; // scale to 10 bits + if (reg < 0 || reg > (int32_t)0x3ff) + clipped = 0x3ff; + } +// printf("ADC to 9/10 bits 0x%x %d\n", reg, reg); + if (clipped) { + AVR_LOG(avr, LOG_WARNING, + "ADC: channel %d clipped %u/%u VREF %d\n", + p->current_muxi, reg, clipped, vref); + reg = clipped; } + reg &= 0x3ff; reg <<= shift; -// printf("ADC to 10 bits %x shifted %d\n", reg, shift); - avr->data[p->r_adcl] = reg; - avr->data[p->r_adch] = reg >> 8; - p->read_status = 1; - return avr_core_watch_read(avr, addr); +// printf("ADC to 9/10 bits %x shifted %d\n", reg, shift); + p->result = reg; + + /* Schedule the interrupt in 11 ADC cycles. */ + + avr_cycle_timer_register(avr, p->current_prescale * 11, + avr_adc_int_raise, p); + return 0; } /* @@ -127,25 +164,26 @@ avr_adc_read_l( * Consequently, if the result is left adjusted and no more than 8-bit * precision is required, it is sufficient to read ADCH. * Otherwise, ADCL must be read first, then ADCH." - * So here if the H is read before the L, we still call the L to update the - * register value. */ + +static uint8_t +avr_adc_read_l( + struct avr_t * avr, avr_io_addr_t addr, void * param) +{ + avr_adc_t * p = (avr_adc_t *)param; + + p->read_status = 1; // Set the update interlock. + return avr_core_watch_read(avr, addr); +} + static uint8_t avr_adc_read_h( struct avr_t * avr, avr_io_addr_t addr, void * param) { avr_adc_t * p = (avr_adc_t *)param; - // no "break" here on purpose - switch (p->read_status) { - case 0: - avr_adc_read_l(avr, p->r_adcl, param); - FALLTHROUGH - case 1: - p->read_status = 2; - FALLTHROUGH - default: - return avr_core_watch_read(avr, addr); - } + + p->read_status = 0; // Clear the update interlock. + return avr_core_watch_read(avr, addr); } static void @@ -211,47 +249,60 @@ avr_adc_write_adcsra( avr_adc_t * p = (avr_adc_t *)param; uint8_t adsc = avr_regbit_get(avr, p->adsc); uint8_t aden = avr_regbit_get(avr, p->aden); + uint8_t new_aden; avr->data[p->adsc.reg] = v; + new_aden = avr_regbit_get(avr, p->aden); // can't write zero to adsc if (adsc && !avr_regbit_get(avr, p->adsc)) { avr_regbit_set(avr, p->adsc); v = avr->data[p->adsc.reg]; } - if (!aden && avr_regbit_get(avr, p->aden)) { + if (!aden && new_aden) { // first conversion p->first = 1; AVR_LOG(avr, LOG_TRACE, "ADC: Start AREF %d AVCC %d\n", avr->aref, avr->avcc); } if (aden && !avr_regbit_get(avr, p->aden)) { // stop ADC + + avr_cycle_timer_cancel(avr, avr_adc_convert, p); avr_cycle_timer_cancel(avr, avr_adc_int_raise, p); avr_regbit_clear(avr, p->adsc); v = avr->data[p->adsc.reg]; // Peter Ross pross@xvid.org } - if (!adsc && avr_regbit_get(avr, p->adsc)) { + if (new_aden && !adsc && avr_regbit_get(avr, p->adsc)) { // start one! - uint8_t muxi = avr_regbit_get_array(avr, p->mux, ARRAY_SIZE(p->mux)); - union { - avr_adc_mux_t mux; - uint32_t v; - } e = { .mux = p->muxmode[muxi] }; - avr_raise_irq(p->io.irq + ADC_IRQ_OUT_TRIGGER, e.v); + + /* Copy mux, prescaler and ADSRB settings, as they may change + * before conversion. + */ + + p->current_muxi = avr_regbit_get_array(avr, p->mux, + ARRAY_SIZE(p->mux)); + p->current_refi = avr_regbit_get_array(avr, p->ref, + ARRAY_SIZE(p->ref)); // clock prescaler are just a bit shift.. and 0 means 1 - uint32_t div = avr_regbit_get_array(avr, p->adps, ARRAY_SIZE(p->adps)); + + uint32_t div = avr_regbit_get_array(avr, p->adps, + ARRAY_SIZE(p->adps)); if (!div) div++; - div = avr->frequency >> div; if (p->first) - AVR_LOG(avr, LOG_TRACE, "ADC: starting at %uKHz\n", div / 13 / 100); - div /= p->first ? 25 : 13; // first cycle is longer - - avr_cycle_timer_register(avr, - avr_hz_to_cycles(avr, div), - avr_adc_int_raise, p); - } + AVR_LOG(avr, LOG_TRACE, "ADC: starting at %uKHz\n", + (avr->frequency >> div) / 13 / 100); + div = (1 << div); + div *= (p->first ? 14 : 2); // first conversion is longer + p->current_prescale = div; + avr_cycle_timer_register(avr, div, avr_adc_convert, p); + p->current_extras.bipolar = + p->bin.reg && avr_regbit_get(avr, p->bin); + p->current_extras.negate = + p->ipr.reg && avr_regbit_get(avr, p->ipr); + p->current_extras.adjust = avr_regbit_get(avr, p->adlar); + } avr_core_watch_write(avr, addr, v); avr_adc_configure_trigger(avr, addr, v, param); } diff --git a/simavr/sim/avr_adc.h b/simavr/sim/avr_adc.h index f2e6eda..1bfee9e 100644 --- a/simavr/sim/avr_adc.h +++ b/simavr/sim/avr_adc.h @@ -131,14 +131,31 @@ typedef struct avr_adc_t { // use ADIF and ADIE bits avr_int_vector_t adc; - /* + avr_adc_mux_t muxmode[64]; // maximum 6 bits of mux modes + + /* * runtime bits */ - avr_adc_mux_t muxmode[64];// maximum 6 bits of mux modes + uint16_t adc_values[16]; // current values on the ADCs uint16_t temp; // temp sensor reading uint8_t first; uint8_t read_status; // marked one when adcl is read + + /* Conversion parameters saved at start (ADSC is set). */ + + uint8_t current_muxi; + uint8_t current_refi; + uint8_t current_prescale; + struct { + unsigned int bipolar : 1; // BIN bit. + unsigned int negate : 1; // IPR bit. + unsigned int adjust : 1; // ADLAR bit. + } current_extras; + + /* Buffered conversion result. */ + + uint16_t result; } avr_adc_t; void avr_adc_init(avr_t * avr, avr_adc_t * port); diff --git a/tests/attiny85_adc_test.c b/tests/attiny85_adc_test.c new file mode 100644 index 0000000..044ddc4 --- /dev/null +++ b/tests/attiny85_adc_test.c @@ -0,0 +1,127 @@ +/* + attiny85_adc_test.c + + Copyright 2021 Giles Atkinson + + This file is part of simavr. + + simavr 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 3 of the License, or + (at your option) any later version. + + simavr 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 simavr. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "avr_mcu_section.h" + +AVR_MCU(F_CPU, "attiny85"); +AVR_MCU_VOLTAGES(5000, 5000, 3000) // VCC, AVCC, VREF - millivolts. + +/* No UART in tiny85, so simply write to unimplemented register ISIDR. */ + +static int uart_putchar(char c, FILE *stream) { + if (c == '\n') + uart_putchar('\r', stream); + USIDR = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + +/* Table of values for ADMUX and ADSRB. */ + +static struct params { + uint8_t mux, srb; +} params[] = { + {0x90, 0 }, // 2.56V ref, input ADC0 + {0x81, 0 }, // 1.10V ref, input ADC1 + {0x82, 0 }, // 1.10V ref, input ADC2, overflow + {0x83, 0 }, // 1.10V ref, input ADC3, zero + + {0x88, 0}, // 1.10V ref, ADC0/ADC0 differential + {0xa1, 0}, // 1.10V ref, ADC1, left adjusted + {0x96, 0}, // 2.56V ref, ADC2/ADC3 differential. signed + {0x97, 0x80}, // 2.56V ref, ADC2/ADC3 differential, signed, X20 + + {0x8B, 0}, // 1.10V ref, ADC0/ADC1 differential X20 + {0x81, 0xa0}, // 1.10V ref, input ADC1, BIN and IPR on. + {0x96, 0x80}, // 2.56V ref, ADC2/ADC3 differential, signed + {0x96, 0x20}, // 2.56V ref, ADC2/ADC3 differential, IPR + + {0x9a, 0x80}, // 2.56V ref, ADC0/ADC1 differential, signed +ve overflow + {0x9a, 0x80}, // 2.56V ref, ADC0/ADC1 differential, signed, positive + {0x86, 0x80}, // 1.10V ref, ADC2/ADC3 differential, signed, -ve overflow + {0x13, 0 }, // 3.00 V external ref, input ADC3 +}; + +#define NUM_SUBTESTS (sizeof params / sizeof params[0]) + +static int index_i, index_o; +static uint16_t int_results[NUM_SUBTESTS + 2]; + +ISR(ADC_vect) +{ + /* Write the next ADCMUX/ADSRB settings. */ + + if (index_i >= NUM_SUBTESTS) { + ADCSRA &= ~(1 << ADATE); + return; + } + ADMUX = params[index_i].mux; + ADCSRB = params[index_i].srb; + index_i++; +} + +int main(void) +{ + int i; + + stdout = &mystdout; + printf("ADC"); + + /* Turn on the ADC. */ + + ADCSRA = (1 << ADEN) + 5; // Enable, clock scale = 32 + + /* Do conversions. */ + + for (i = 0; i < NUM_SUBTESTS; ++i) { + ADMUX = params[i].mux; + ADCSRB = params[i].srb; + ADCSRA = (1 << ADEN) + (1 << ADSC) + 5; + while ((ADCSRA & (1 << ADIF)) == 0) + ; + printf(" %d", ADC); + } + uart_putchar('\n', stdout); + + /* Do it again with interrupts. printf() is too slow to send the + * results in real time, even with maximum pre-scaler ratio. + */ + + sei(); + ADCSRA = (1 << ADEN) + (1 << ADSC) + (1 << ADATE) + (1 << ADIE) + 4; + + while (index_o < NUM_SUBTESTS + 2) { + sleep_cpu(); + int_results[index_o++] = ADC; + } + for (i = 0; i < NUM_SUBTESTS + 2; ++i) + printf(" %d", int_results[i]); + cli(); + sleep_cpu(); +} diff --git a/tests/test_attiny85_adc_test.c b/tests/test_attiny85_adc_test.c new file mode 100644 index 0000000..60b073c --- /dev/null +++ b/tests/test_attiny85_adc_test.c @@ -0,0 +1,101 @@ +#include +#include +#include "tests.h" +#include "avr_adc.h" + +/* Start of the ADC's IRQ list. */ + +static avr_irq_t *base_irq; + +/* Table of voltages to apply to each input in turn. */ + +static uint32_t volts[] = { + 1283, // 2.56V Ref. just over half full scale + 990, // 1.1 V ref 0.9 FS + 1200, // over-voltage + 0, // zero + + 1040, // ADC0/ADC0 differential + 386, // ADC1, left adjust + 100, // 2.56V ADC2/ADC3 differential + 210, // 2.56V ADC2/ADC3 differential, signed X20 + + 400, // 1.10V ref, ADC0/ADC1 differential X20 + 2, // 1.10V ref, ADC1 + 100, // 2.56V ref, ADC2/ADC3 differential, signed + 2000, // 2.56V ref, ADC2/3 diff, signed, IPR + + 3000, // 2.56V ref, ADC0/1 diff, signed, overflow + 2215, // 2.56V ref, ADC0/1 diff, signed + 700, // 1.10V ref, ADC2/3 diff, signed -ve overflow + 2222, // AREF (3V) ADC3 +}; + +static unsigned int index; + +/* Callback for A-D conversion sampling. */ + +static void conversion(struct avr_irq_t *irq, uint32_t value, void *param) +{ + int i; + union { + avr_adc_mux_t request; + uint32_t v; + } u = { .v = value }; + + if (index >= ARRAY_SIZE(volts)) { + /* This happens when starting with interrupts. + * Do the sub-tests again after repeating the last one twice, + * once for the initial conversion and once again for the conversion + * that has already started at the time of the first interrupt. + * That agrees with the data sheet. + */ + + if (index == ARRAY_SIZE(volts)) + index++; + else + index = 0; + return; + } + + i = index & 3; + if (i != u.request.src && + !(u.request.src + 1 == i && u.request.diff == i)) { + /* Requested input not expected. */ + + fail("Simulator requested input %d, but %d expected, index %d\n", + u.request.src, i, index); + } + + avr_raise_irq(base_irq + i, volts[index]); + index++; +} + +int main(int argc, char **argv) { + static const char *expected = "ADC 512 920 1023 0" + " 0 22912 39 585" + " 260 1 1003 759" + " 511 156 512 757\r\n" + " 757 757" + " 512 920 1023 0" + " 0 22912 39 585" + " 260 1 1003 759" + " 511 156 512 757"; + avr_t *avr; + + tests_init(argc, argv); + avr = tests_init_avr("attiny85_adc_test.axf"); + + /* Request callback when a value is sampled for conversion. */ + + base_irq = avr_io_getirq(avr, AVR_IOCTL_ADC_GETIRQ, 0); + avr_irq_register_notify(base_irq + ADC_IRQ_OUT_TRIGGER, + conversion, NULL); + + /* Run program and check results. */ + + tests_assert_register_receive_avr(avr, 100000, expected, + (avr_io_addr_t)0x2f /* &USIDR */); + tests_success(); + return 0; +} diff --git a/tests/tests.c b/tests/tests.c index 8161eba..7f83657 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -153,8 +153,8 @@ struct output_buffer { int maxlen; }; -/* static void buf_output_cb(avr_t *avr, avr_io_addr_t addr, uint8_t v, */ -/* void *param) { */ +/* Callback for receiving data via an IRQ. */ + static void buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) { struct output_buffer *buf = param; if (!buf) @@ -171,6 +171,16 @@ static void buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) { buf->str[buf->currlen] = 0; } +/* Callback for receiving data directly from a register, + * after calling avr_register_io_write(). + */ + +static void reg_output_cb(struct avr_t *avr, avr_io_addr_t addr, + uint8_t v, void *param) +{ + buf_output_cb(NULL, v, param); +} + static void init_output_buffer(struct output_buffer *buf) { buf->str = malloc(128); buf->str[0] = 0; @@ -179,6 +189,25 @@ static void init_output_buffer(struct output_buffer *buf) { buf->maxlen = 4096; } +static void tests_assert_xxxx_receive_avr(avr_t *avr, + unsigned long run_usec, + struct output_buffer *buf, + const char *expected) +{ + enum tests_finish_reason reason = tests_run_test(avr, run_usec); + + if (reason == LJR_CYCLE_TIMER) { + if (strcmp(buf->str, expected) == 0) { + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "Output is correct and complete.", run_usec); + } + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "Output so far: \"%s\"", run_usec, buf->str); + } + if (strcmp(buf->str, expected) != 0) + _fail(NULL, 0, "Outputs differ: expected \"%s\", got \"%s\"", expected, buf->str); +} + void tests_assert_uart_receive_avr(avr_t *avr, unsigned long run_usec, const char *expected, @@ -186,19 +215,10 @@ void tests_assert_uart_receive_avr(avr_t *avr, struct output_buffer buf; init_output_buffer(&buf); - avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUTPUT), - buf_output_cb, &buf); - enum tests_finish_reason reason = tests_run_test(avr, run_usec); - if (reason == LJR_CYCLE_TIMER) { - if (strcmp(buf.str, expected) == 0) { - _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " - "UART output is correct and complete.", run_usec); - } - _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " - "UART output so far: \"%s\"", run_usec, buf.str); - } - if (strcmp(buf.str, expected) != 0) - _fail(NULL, 0, "UART outputs differ: expected \"%s\", got \"%s\"", expected, buf.str); + avr_irq_register_notify( + avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ(uart), + UART_IRQ_OUTPUT), buf_output_cb, &buf); + tests_assert_xxxx_receive_avr(avr, run_usec, &buf, expected); } void tests_assert_uart_receive(const char *elfname, @@ -213,6 +233,28 @@ void tests_assert_uart_receive(const char *elfname, uart); } +void tests_assert_register_receive_avr(avr_t *avr, + unsigned long run_usec, + const char *expected, + avr_io_addr_t reg_addr) +{ + struct output_buffer buf; + + init_output_buffer(&buf); + avr_register_io_write(avr, reg_addr, reg_output_cb, &buf); + tests_assert_xxxx_receive_avr(avr, run_usec, &buf, expected); +} + +void tests_assert_register_receive(const char *elfname, + unsigned long run_usec, + const char *expected, + avr_io_addr_t reg_addr) +{ + avr_t *avr = tests_init_avr(elfname); + + tests_assert_register_receive_avr(avr, run_usec, expected, reg_addr); +} + void tests_assert_cycles_at_least(unsigned long n) { if (tests_cycle_count < n) _fail(NULL, 0, "Program ran for too few cycles (%" diff --git a/tests/tests.h b/tests/tests.h index 1496b93..e378f83 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -32,6 +32,14 @@ void tests_assert_uart_receive_avr(avr_t *avr, unsigned long run_usec, const char *expected, char uart); +void tests_assert_register_receive(const char *elfname, + unsigned long run_usec, + const char *expected, + avr_io_addr_t reg_addr); +void tests_assert_register_receive_avr(avr_t *avr, + unsigned long run_usec, + const char *expected, + avr_io_addr_t reg_addr); void tests_assert_cycles_at_least(unsigned long n); void tests_assert_cycles_at_most(unsigned long n); -- 2.39.5