Close

v06 MP indicator fading

A project log for NeuroBytes

Build your own nervous system!

zakqwyzakqwy 01/06/2016 at 15:480 Comments

Yesterday I put together a few functions that fade the twelve membrane potential LEDs using 6-bit BCM at 160 Hz:

A good deal of this was developed at various Minneapolis coffee shops filled with curious patrons asking me questions about green LEDs. I was happy to take a few tangents to discuss neuroscience. As demonstrated above, flicker is noticeable when the NeuroBytes are waved back and forth rapidly, but I don't think it's too bad; after all, these devices will generally be used on a static surface. 160 Hz is a decent bit faster than those annoying LED Christmas lights, after all.

The functions started with Nigel Batten's excellent BCM example. I liked how he used the 'tick' counter to trigger stuff in the main loop from the ISR, keeping the interrupt stuff brief. Most of the other code is different, as I'm dealing with a 3x4 LED matrix spanning three different I/O ports (A, B, and D) and didn't use a pre-encoding function. I'm not going to add this firmware to the GitHub repo at this point as it's far from finished/debugged, but I'm posting it here if anyone has any comments or wants to use it for something else (it's all GPL v3). My code has slightly improved since the v04 days, but I'm still learning the basics re: bit manipulation and speed. I also added a sweet ASCII diagram for I/O reference.

(note: I didn't add any commentary after the code block, so feel free to stop reading this post now)

/*
	NeuroBytes v06 test program
	Fading MP LEDs via BCM, attempt 2
	Copyright 2016 by Zach Fredin
	zach@neurotinker.com

This file is part of NeuroBytes v0.6.

NeuroBytes v0.6 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.

NeuroBytes v0.6 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 NeuroBytes v0.6. If not, see <http://www.gnu.org/licenses/>
*/


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

/*
	   /\
	  /  \
	 /    \
	/      \___________________________________
	\                                          \
	 \                NeuroBytes v0.6           \
	  \                                          \
	   \         D D D D D D D D D D D D D A     |
           |         1 2 3 4 5 6 7 8 9 9 1 1 1 P     |
	   /                             0 1 2       |
	  /                                          /
	 /                                          /
	/       ___________________________________/
	\      /
	 \    /
	  \  /
	   \/
*/

/*	Membrane potential brightness array
	Array dimensions: MP_brightness[cathode][anode]

		IND-A1		IND-A2		IND-A3

	IND-C1	D1:[1][1]	D5:[2][1]	D9:[3][1]

	IND-C2	D2:[1][2]	D6:[2][2]	D10:[3][2]

	IND-C3	D3:[1][3]	D7:[2][3]	D11:[3][3]

	IND-C4	D4:[1][4]	D8:[2][4]	D12:[3][4] */
volatile uint8_t MP_brightness[4][3] = {  
	{1,31,15},
	{3,63,7},
	{7,63,3},
	{15,31,1}
};		

volatile uint8_t g_tick = 0; //flips to non-zero when time to update (via ISR)
volatile uint8_t g_bitpos = 0; //which bit is currently being checked
volatile uint8_t MP_currentAnode = 0; 

ISR(TIMER0_COMPA_vect) { 
/* 	This interrupt service routine makes ticks fire at exponential intervals. It also dwells
	on each interval for three (hopefully) equally-spaced iterations to set the three anodes.
*/
	g_tick = 1; //fires tick in main loop	
	TCNT0 = 0; //resets Timer/Counter 0
	MP_currentAnode++; //iterates anode
	if(MP_currentAnode > 2) { //go to the next bit if we've hit all three anodes
		MP_currentAnode = 0;
		OCR0A <<= 1; //double the delay
		g_bitpos++; //move one bit left
		if(g_bitpos > 5) {
			OCR0A = 1; //reset the compare register to timer refresh rate (see TCCR0A)
			g_bitpos = 0; //go back to the LSB
		}
	}
}

void systemInit(void) {
/* set up Timer/Counter0 */
	TCCR0A |= ((1<<CTC0)  | (1<<CS02)); //CTC, clk/64
	TCNT0 = 0; 
	OCR0A = 1; //presets Output Compare Register A
	TIMSK0 |= (1<<OCIE0A); //enables Output Compare Match A Interrupt

/* set up membrane potential indicator LEDs */
	DDRA |= (1<<PA3);
	DDRB |= ((1<<PB7) | (1<<PB6) | (1<<PB0));
	DDRD |= ((1<<PD5) | (1<<PD6) | (1<<PD7));
	
/* preset all ports low */
	PORTA = 0;
	PORTB = 0;
	PORTD = 0;

/* misc */
	sei(); //enable global interrupts
}

void MP_off(void) { //sets all cathodes, clears all anodes
	PORTD |= ((1<<PD5) | (1<<PD6) | (1<<PD7));
	PORTB |= (1<<PB0);
	PORTB &= ~((1<<PB7) | (1<<PB6));
	PORTA &= ~(1<<PA3);
}

void MP_setAnode(uint8_t anode) { //sets designated membrane potential anode
	switch(anode) {
		case 0:
			PORTB |= (1<<PB7);
			break;
		case 1:
			PORTB |= (1<<PB6);
			break;
		case 2:
			PORTA |= (1<<PA3);
			break;
	}
}

void MP_clearCathode(uint8_t cathode) { //clears designated membrane potential cathode
	switch(cathode) {
		case 0: 
			PORTD &= ~(1<<PD5);
			break;
		case 1:
			PORTD &= ~(1<<PD6);
			break;
		case 2:
			PORTD &= ~(1<<PD7);
			break;
		case 3:
			PORTB &= ~(1<<PB0);
			break;
	}
}

int main(void) {
	systemInit();
	uint8_t MP_currentCathode; 
	for(;;) {
		while(g_tick==0); { /*blank idle code waiting for ISR */ }
		g_tick = 0; //consumes tick when routine starts
		MP_off();	
		MP_setAnode(MP_currentAnode);
		for (MP_currentCathode = 0; MP_currentCathode < 4; MP_currentCathode++) {
			if ((MP_brightness[MP_currentCathode][MP_currentAnode]) & (1<<g_bitpos)) {
				MP_clearCathode(MP_currentCathode);
			}
		}
	}

} 

Discussions