Skip to content

Internal pull-up algorithm test #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions examples/board_keypress/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#
# Copyright 2008, 2009 Michel Pollet <[email protected]>
#
# 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 <http://www.gnu.org/licenses/>.

target= keypress
firm_src = ${wildcard at*${board}.c}
firmware = ${firm_src:.c=.axf}
simavr = ../../

IPATH = .
IPATH += ../parts
IPATH += ${simavr}/include
IPATH += ${simavr}/simavr/sim

VPATH = .
VPATH += ../parts

LDFLAGS += -lpthread

include ../Makefile.opengl

all: obj ${firmware} ${target}

include ${simavr}/Makefile.common

board = ${OBJ}/${target}.elf

${board} : ${OBJ}/button.o
${board} : ${OBJ}/${target}.o

${target}: ${board}
@echo $@ done

clean: clean-${OBJ}
rm -rf *.a *.axf ${target} *.vcd *.hex
24 changes: 24 additions & 0 deletions examples/board_keypress/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

This contains a sample program to demonstrate the use of simavr
using 'custom' code, and own "peripherals". It shows how it is
possible to "hook" code to the AVR pins, and also how to make
"peripherals" and also hook them up to AVR pins.

The demo will display two LEDs, one connected to the PORTB5 pin
and second to the PORTD7 pin of the mega48.
Two other pins, PORTD0 and PORTD1, are configured as internally
pulled-up inputs and push buttons are hooked-up to them.

Firmware transfers the state of every input pin to corresponding
output pin in a loop. The state of PORTD0 pin is copied to the PORTB5
and PORTD1 is copied to PORTD7.

On startup, both LEDs should lit, this demonstrates a wark of internall pull-ups.
If you hit '1' key on the keyboard, PORTD0 and PORTB5 should go into LOW state
and the LED will be turned off.
On '2' key hit the same will happen with the second LED.


The display uses opengl and "glut" so it should be very portable.
On most linux you will need "freeglut-dev" package.

54 changes: 54 additions & 0 deletions examples/board_keypress/atmega48_keypress.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
atmega48_keypress.c
Copyright 2017 Al Popov
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 <http://www.gnu.org/licenses/>.
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

// for linker, emulator, and programmer's sake
#include "avr_mcu_section.h"
AVR_MCU(F_CPU, "atmega48");

int main()
{
// Setup output pins for LEDs
DDRB = _BV(PINB5);
DDRD = _BV(PIND7);
// Turn on internal pull-ups on input pins
PORTD = _BV(PIND0) | _BV(PIND1);

sei();

for (;;) {
if (PIND & _BV(PIND0)) {
PORTB |= _BV(PINB5);
} else {
PORTB &= ~_BV(PINB5);
}
if (PIND & _BV(PIND1)) {
PORTD |= _BV(PIND7);
} else {
PORTD &= ~_BV(PIND7);
}
//sleep_mode();
}
}

251 changes: 251 additions & 0 deletions examples/board_keypress/keypress.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
keypress.c
Copyright 2017 Al Popov
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 <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
#if __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <pthread.h>

#include "sim_avr.h"
#include "avr_ioport.h"
#include "sim_elf.h"
#include "sim_gdb.h"
//#include "sim_vcd_file.h"

#include "button.h"

button_t button1;
button_t button2;
int do_button_press1 = 0;
int do_button_press2 = 0;
avr_t * avr = NULL;
//avr_vcd_t vcd_file;
uint8_t pin_state = 0;

float pixsize = 64;
int window;

/*
* called when some button is pressed (i.e. pin state on the port D is changed)
* so lets update our buffer
*/
void output_pin_changed_hook(struct avr_irq_t * irq, uint32_t value, void * param)
{
int pin_no = irq->irq;
if (pin_no == 5)
pin_no = 0;
else if (pin_no == 7)
pin_no = 1;
pin_state = (pin_state & ~(1 << pin_no)) | (value << pin_no);
}

void displayCB(void) /* function called whenever redisplay needed */
{
// OpenGL rendering goes here...
glClear(GL_COLOR_BUFFER_BIT);

// Set up modelview matrix
glMatrixMode(GL_MODELVIEW); // Select modelview matrix
glLoadIdentity(); // Start with an identity matrix

float grid = pixsize;
float size = grid * 0.8;
glBegin(GL_QUADS);
glColor3f(1,0,0);

for (int di = 0; di < 8; di++) {
char on = (pin_state & (1 << di)) != 0;
if (on) {
float x = (di) * grid;
float y = 0; //(si * grid * 8) + (di * grid);
glVertex2f(x + size, y + size);
glVertex2f(x, y + size);
glVertex2f(x, y);
glVertex2f(x + size, y);
}
}

glEnd();
glutSwapBuffers();
//glFlush(); /* Complete any pending operations */
}

void keyCB(unsigned char key, int x, int y) /* called on key press */
{
if (key == 'q')
exit(0);
//static uint8_t buf[64];
switch (key) {
case 'q':
case 0x1f: // escape
exit(0);
break;
case '1':
do_button_press1++; // pass the message to the AVR thread
break;
case '2':
do_button_press2++; // pass the message to the AVR thread
break;
}
}

// gl timer. if the pin have changed states, refresh display
void timerCB(int i)
{
static uint8_t oldstate = 0xff;
// restart timer
glutTimerFunc(1000/64, timerCB, 0);

if (oldstate != pin_state) {
oldstate = pin_state;
glutPostRedisplay();
}
}

//#undef AVR_IOPORT_INTRN_PULLUP_IMP
static void * avr_run_thread(void * oaram)
{
int b_press1 = do_button_press1;
int b_press2 = do_button_press2;
int b_buttons_hooked_up = 0;

while (1) {
int b_user_activity = 0;
avr_run(avr);
if (do_button_press1 != b_press1) {
b_press1 = do_button_press1;
printf("Button 1 pressed\n");
button_press(&button1, 1000000);
b_user_activity = 1;
}
if (do_button_press2 != b_press2) {
b_press2 = do_button_press2;
printf("Button 2 pressed\n");
button_press(&button2, 1000000);
b_user_activity = 2;
}
#ifdef AVR_IOPORT_INTRN_PULLUP_IMP
// If we wish to take a control over internally pulled-up input pin,
// that is, we have a "low output impedance source" connected to the pin,
// we must explicitly inform simavr about it.
if (!b_buttons_hooked_up && b_user_activity) {
avr_raise_irq_float(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'),
IOPORT_IRQ_PIN0_SRC_IMP), 0, 1);
avr_raise_irq_float(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'),
IOPORT_IRQ_PIN0_SRC_IMP + 1), 0, 1);
b_buttons_hooked_up = 1;
}
// Otherwise simavr internall pull-ups handling is active and will "override"
// the pin state in some situations.
#endif //AVR_IOPORT_INTRN_PULLUP_IMP
}
return NULL;
}


int main(int argc, char *argv[])
{
elf_firmware_t f;
const char * fname = "atmega48_keypress.axf";
//char path[256];

// sprintf(path, "%s/%s", dirname(argv[0]), fname);
// printf("Firmware pathname is %s\n", path);
elf_read_firmware(fname, &f);

printf("firmware %s f=%d mmcu=%s\n", fname, (int)f.frequency, f.mmcu);

avr = avr_make_mcu_by_name(f.mmcu);
if (!avr) {
fprintf(stderr, "%s: AVR '%s' not known\n", argv[0], f.mmcu);
exit(1);
}
avr_init(avr);
avr_load_firmware(avr, &f);

// initialize our 'peripherals'
button_init(avr, &button1, "button0");
button_init(avr, &button2, "button1");
// "connect" the output irq of our buttons to the AVR input pins of port D
avr_connect_irq(
button1.irq + IRQ_BUTTON_OUT,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 0));
avr_connect_irq(
button2.irq + IRQ_BUTTON_OUT,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 1));

// connect output pins of the AVR to our callback
avr_irq_register_notify(
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 5),
output_pin_changed_hook,
NULL);
avr_irq_register_notify(
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('D'), 7),
output_pin_changed_hook,
NULL);

// even if not setup at startup, activate gdb if crashing
avr->gdb_port = 1234;
if (0) {
//avr->state = cpu_Stopped;
avr_gdb_init(avr);
}

// 'raise' it, it's a "pullup"
//avr_raise_irq(button0.irq + IRQ_BUTTON_OUT, 1);

printf( "Demo launching:\n"
" Press 'q' to quit\n\n"
" Press '1' to extinguish the first LED\n"
" Press '2' to extinguish the second LED\n"
);

/*
* OpenGL init, can be ignored
*/
glutInit(&argc, argv); /* initialize GLUT system */

glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowSize(8 * pixsize, 1 * pixsize); /* width=400pixels height=500pixels */
window = glutCreateWindow("Simavr key press test"); /* create window */

// Set up projection matrix
glMatrixMode(GL_PROJECTION); // Select projection matrix
glLoadIdentity(); // Start with an identity matrix
glOrtho(0, 8 * pixsize, 0, 1 * pixsize, 0, 10);
glScalef(1,-1,1);
glTranslatef(0, -1 * pixsize, 0);

glutDisplayFunc(displayCB); /* set window's display callback */
glutKeyboardFunc(keyCB); /* set window's key callback */
glutTimerFunc(1000 / 24, timerCB, 0);

// the AVR run on it's own thread. it even allows for debugging!
pthread_t run;
pthread_create(&run, NULL, avr_run_thread, NULL);

glutMainLoop();
}
208 changes: 208 additions & 0 deletions tests/atmega88_pullups_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
atmega88_pullups_test.c
Copyright 2017 Al Popov
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 <http://www.gnu.org/licenses/>.
*/

#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/cpufunc.h>

/*
* This test verifies correctness of handling AVR internal pull-ups
* The macro adds a section to the ELF file with useful
* information for the simulator
*/
#include "avr_mcu_section.h"
AVR_MCU(F_CPU, "atmega88");
// tell simavr to listen to commands written in this (unused) register
//AVR_MCU_SIMAVR_COMMAND(&GPIOR0);

void
init_uart()
{
UCSR0C |= (3 << UCSZ00); // 8 bits
// see http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html
#define BAUD 38400
#include <util/setbaud.h>
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X
UCSR0A |= (1 << U2X0);
#else
UCSR0A &= ~(1 << U2X0);
#endif

// enable receiver & transmitter
UCSR0B |= (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0);
}

static int
uart_putchar(char c, FILE *stream)
{
if (c == '\n')
uart_putchar('\r', stream);
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
return 0;
}

/*
static int
uart_getchar(FILE *stream)
{
// Wait for data to be received
while ( !(UCSR0A & (1<<RXC0)) )
;
// Get and return received data from buffer
char data = UDR0; //Temporarly store received data
if(data == '\r')
data = '\n';
uart_putchar(data, stream); //Send to console what has been received, so we can see when typing
return data;
}
*/

//static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
_FDEV_SETUP_WRITE);


volatile uint8_t bindex = 0;
uint8_t buffer[80];
volatile uint8_t done = 0;

ISR(USART_RX_vect)
{
uint8_t b = UDR0;
buffer[bindex++] = b;
buffer[bindex] = 0;
if (b == '\n')
done++;
}

static void
reset_buffer()
{
cli();
done = 0;
bindex = 0;
buffer[bindex] = 0;
sei();
}

static void
press_button(int pin_c)
{
reset_buffer();
printf("~p%c\n", pin_c);
while (!done) sleep_cpu();
reset_buffer();
}

static void
release_button(int pin_c)
{
reset_buffer();
printf("~r%c\n", pin_c);
while (!done) sleep_cpu();
reset_buffer();
}

static uint8_t
read_pin(int pin_c)
{
uint8_t mask = (1 << (pin_c - '0'));
return (PIND & mask)?1:0;
}

static void
translate_pin_state(int pin_c, uint8_t exp)
{
uint8_t pin_state = read_pin(pin_c);
uint8_t output_mask = (1 << (pin_c - '0' + 3));
uint8_t portd = PORTD;
if (pin_state)
portd |= output_mask;
else
portd &= ~output_mask;
PORTD = portd;

reset_buffer();
printf("PIND%c expected=%s, actual=%s\n", pin_c, (exp?"HIGH":"LOW"), (pin_state?"HIGH":"LOW"));
while (!done);// sleep_cpu();
reset_buffer();
}

int main()
{
// this tell simavr to put the UART in loopback mode
//GPIOR0 = SIMAVR_CMD_UART_LOOPBACK;

stdout = &mystdout;
init_uart();

DDRD = 0xE0;
PORTD = 0x1C;

sei();

reset_buffer();
printf("Read internally pulled-up input pins on PIND2:4 and mirror its state to PORTD5:7 outputs\n");

while (!done);// sleep_cpu();
reset_buffer();

translate_pin_state('2', 1);
translate_pin_state('3', 1);
translate_pin_state('4', 1);

press_button('2');
translate_pin_state('2', 0);
translate_pin_state('3', 1);
translate_pin_state('4', 1);
release_button('2');
translate_pin_state('2', 1);
translate_pin_state('3', 1);
translate_pin_state('4', 1);

press_button('3');
translate_pin_state('2', 1);
translate_pin_state('3', 0);
translate_pin_state('4', 1);
release_button('3');
translate_pin_state('2', 1);
translate_pin_state('3', 1);
translate_pin_state('4', 1);

press_button('4');
translate_pin_state('2', 1);
translate_pin_state('3', 1);
translate_pin_state('4', 0);
release_button('4');
translate_pin_state('2', 1);
translate_pin_state('3', 1);
translate_pin_state('4', 1);

cli();
sleep_cpu();

}
182 changes: 182 additions & 0 deletions tests/test_atmega88_pullups_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
test_atmega88_pullups_test.c
Copyright 2017 Al Popov
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 <http://www.gnu.org/licenses/>.
*/
#include "tests.h"
#include "sim_avr.h"
#include "sim_elf.h"
#include "sim_core.h"
#include "avr_uart.h"
#include "avr_ioport.h"
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>

enum {
IRQ_UART_TERM_BYTE_IN = 0,
IRQ_UART_TERM_BYTE_OUT,
IRQ_UART_TERM_COUNT
};

typedef struct uart_TERM_t {
avr_irq_t * irq; // irq list
} uart_TERM_t;

static const char * irq_names[IRQ_UART_TERM_COUNT] = {
[IRQ_UART_TERM_BYTE_IN] = "8<uart_TERM.in",
[IRQ_UART_TERM_BYTE_OUT] = "8>uart_TERM.out",
};

uart_TERM_t uart_term;

struct output_buffer {
char *str;
int currlen;
int alloclen;
int maxlen;
int head;
avr_t *avr;
};

static void
buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) {
struct output_buffer *buf = param;
if (!buf)
fail("Internal error: buf == NULL in buf_output_cb()");
if (buf->currlen > buf->alloclen-1)
fail("Internal error");
if (buf->alloclen == 0)
fail("Internal error");
if (buf->currlen == buf->alloclen-1) {
buf->alloclen *= 2;
buf->str = realloc(buf->str, buf->alloclen);
}
buf->str[buf->currlen++] = value;
buf->str[buf->currlen] = 0;
if (value == '\n') {
buf->head = buf->currlen;
avr_irq_t * dst = avr_io_getirq(buf->avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_INPUT);
avr_raise_irq(dst, '1');
avr_raise_irq(dst, '\n');
} else if (value == '\r') {
if ((buf->str[buf->head] == '~')
&& (buf->head < (buf->currlen-2))) {
// simulate the button press or release on the specified pin
int m_pinN = buf->str[buf->head + 2] - '0';
avr_irq_t *irq = avr_io_getirq( buf->avr, AVR_IOCTL_IOPORT_GETIRQ('D'), m_pinN );
if (buf->str[buf->head + 1] == 'p') {
#ifdef AVR_IOPORT_INTRN_PULLUP_IMP
// If we wish to take a control over internally pulled-up input pin,
// that is, we have a "low output impedance source" connected to the pin,
// we must explicitly inform simavr about it.
avr_irq_t *src_imp_irq = avr_io_getirq(buf->avr, AVR_IOCTL_IOPORT_GETIRQ('D'), m_pinN + IOPORT_IRQ_PIN0_SRC_IMP);
avr_raise_irq_float(src_imp_irq, 0, 1);
// Otherwise simavr internall pull-ups handling is active and will "override" the pin state in some situations.
#endif //AVR_IOPORT_INTRN_PULLUP_IMP
avr_raise_irq(irq, 0);
} else if (buf->str[buf->head + 1] == 'r') {
avr_raise_irq(irq, 1);
}
}
}
}

static void init_output_buffer(struct output_buffer *buf, avr_t *avr) {
buf->str = malloc(128);
buf->str[0] = 0;
buf->currlen = 0;
buf->alloclen = 128;
buf->maxlen = 4096;
buf->head = 0;
buf->avr = avr;
}

static void
test_assert_uart_receive_avr(avr_t *avr,
unsigned long run_usec,
const char *expected
) {
struct output_buffer buf;
init_output_buffer(&buf, avr);

memset(&uart_term, 0, sizeof(uart_term));

uart_term.irq = avr_alloc_irq(&avr->irq_pool, 0, IRQ_UART_TERM_COUNT, irq_names);
avr_irq_register_notify(uart_term.irq + IRQ_UART_TERM_BYTE_IN, buf_output_cb, &buf);
//avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUTPUT), buf_output_cb, &buf);
avr_irq_t * src = avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('0'), UART_IRQ_OUTPUT);
avr_connect_irq(src, uart_term.irq + IRQ_UART_TERM_BYTE_IN);

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);
}

int main(int argc, char **argv) {
tests_init(argc, argv);

static const char *expected =
"Read internally pulled-up input pins on PIND2:4 and mirror its state to PORTD5:7 outputs\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
"~p2\r\n"
"PIND2 expected=LOW, actual=LOW\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
"~r2\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
"~p3\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=LOW, actual=LOW\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
"~r3\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
"~p4\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=LOW, actual=LOW\r\n"
"~r4\r\n"
"PIND2 expected=HIGH, actual=HIGH\r\n"
"PIND3 expected=HIGH, actual=HIGH\r\n"
"PIND4 expected=HIGH, actual=HIGH\r\n"
;
avr_t *avr = tests_init_avr("atmega88_pullups_test.axf");

test_assert_uart_receive_avr(avr, 5000000L,
expected);

tests_success();
return 0;
}