I’m having lots of fun diving into this atmel microcontroller stuff. I might need to work on wire management at some point, however…
Now that I have a four-digit hex display that can easily display any sixteen-bit value from memory, naturally I wanted to tie that to something in the real world. The easy choice was a photoresistor, which I tied to A0 pin.
The max value that can be returned from analogRead is 1024, which is 0x3FF on my hex display. In my natural level of room lighting, and in conjunction with a 30k resistor, my readout rests at about 0x18E, about 30% of max value. If I shine my flashlight on the photoresistor, that brings it up to about 0x36D, and covering it with a piece of paper brings it down to about 0x043.
With this adventure I also took the time to learn about and make use of custom timer interrupts. At my day job, I maintain a rather ancient piece of simulator technology, so I spend a fair amount of time reading 1980’s programming and electronics reference manuals, in which there is a heavy emphasis on the precisely-choreographed timing of signals. Consequently, I’ve developed something of a fascination/obsession with timing in circuits.
Anyway, I wanted my main-loop activities to be driven by a steady interrupt signal outside of the loop itself, so I used the 328P’s 16-bit #1 timer (328P microcontroller has three timers available, 0, 1, and 2). With a 16 Mhz clock, x8 prescaler, and OCR value of 20000 (CTC), that provides a precisely 100 Hz interrupt, i.e., an interrupt every 10 ms. 100 Hz was the fastest I needed to loop in order to provide a good frame rate for the hex display. The interrupt simply sets a boolean value, acting as a sort of timing pulse for the main loop.
Ten milliseconds would be plenty of time to accomplish very many operations, even on a 16Mhz microcontroller, but in my case I just needed to drive the display and read a value from A0 pin. The analogRead function needs about 100 microseconds, according to the Arduino reference. I found I needed about 250 microseconds per hex digit in order to provide sufficient voltage to the digits in a 10ms loop, if I remove the resistors.
A side lesson here is that you don’t need resistors for your LEDs if you carefully control the duty cycle of voltage to them. An interesting implication of that would be that increasing your voltage would allow you to decrease you duty cycle as much as you want, i.e., to use shorter voltage pulses.
Here are some helpful links on understanding and using timer interrupts:
https://www.instructables.com/id/Arduino-Timer-Interrupts/
https://www.robotshop.com/community/forum/t/arduino-101-timers-and-interrupts/13072
https://www.youtube.com/watch?v=2kr5A350H7E
Here is the latest code:
/*
Copyright 2020 Christopher Howard
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* Pins 1-12 on MSQ6X41C should be mapped to pins 2-13 on Uno */
#define LP1 2
#define LP2 3
#define LP3 4
#define LP4 5
#define LP5 6
#define LP6 7
#define LP7 8
#define LP8 9
#define LP9 10
#define LP10 11
#define LP11 12
#define LP12 13
#define SA LP11
#define SB LP7
#define SC LP4
#define SD LP2
#define SE LP1
#define SF LP10
#define SG LP5
#define SDOT LP3
enum characters
{
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
NINE, ALPHA, BRAVO, CHARLIE, DELTA, ECHO, FOXTROT, BLANK
};
enum digit {
D_ONE, D_TWO, D_THREE, D_FOUR
};
bool startPulse = false;
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
startPulse = true;
}
void set_led_pins(byte ch, byte dig, bool point) {
/* blank all digits */
PORTD &= 0b10000011;
PORTD |= 0b10000000;
PORTB &= 0b11100110;
PORTB |= 0b00100110;
switch(dig) {
case D_ONE:
PORTB &= 0b11011111;
break;
case D_TWO:
PORTB &= 0b11111011;
break;
case D_THREE:
PORTB &= 0b11111101;
break;
case D_FOUR:
PORTD &= 0b01111111;
break;
}
switch(ch)
{
case BLANK :
break;
case ZERO :
PORTD |= 0b00101100;
PORTB |= 0b00011001;
break;
case ONE :
PORTD |= 0b00100000;
PORTB |= 0b00000001;
break;
case TWO :
PORTD |= 0b01001100;
PORTB |= 0b00010001;
break;
case THREE :
PORTD |= 0b01101000;
PORTB |= 0b00010001;
break;
case FOUR :
PORTD |= 0b01100000;
PORTB |= 0b00001001;
break;
case FIVE :
PORTD |= 0b01101000;
PORTB |= 0b00011000;
break;
case SIX :
PORTD |= 0b01101100;
PORTB |= 0b00011000;
break;
case SEVEN :
PORTD |= 0b00100000;
PORTB |= 0b00010001;
break;
case EIGHT :
PORTD |= 0b01101100;
PORTB |= 0b00011001;
break;
case NINE :
PORTD |= 0b01101000;
PORTB |= 0b00011001;
break;
case ALPHA :
PORTD |= 0b01100100;
PORTB |= 0b00011001;
break;
case BRAVO :
PORTD |= 0b01101100;
PORTB |= 0b00001000;
break;
case CHARLIE :
PORTD |= 0b00001100;
PORTB |= 0b00011000;
break;
case DELTA :
PORTD |= 0b01101100;
PORTB |= 0b00000001;
break;
case ECHO :
PORTD |= 0b01001100;
PORTB |= 0b00011000;
break;
case FOXTROT :
PORTD |= 0b01000100;
PORTB |= 0b00011000;
break;
}
}
void setup()
{
pinMode(LP1, OUTPUT);
pinMode(LP2, OUTPUT);
pinMode(LP3, OUTPUT);
pinMode(LP4, OUTPUT);
pinMode(LP5, OUTPUT);
pinMode(LP6, OUTPUT);
pinMode(LP7, OUTPUT);
pinMode(LP8, OUTPUT);
pinMode(LP9, OUTPUT);
pinMode(LP10, OUTPUT);
pinMode(LP11, OUTPUT);
pinMode(LP12, OUTPUT);
// initialize timer1
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 20000; // compare match register 16MHz/8/100Hz
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS11); // 8 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}
byte readCounter = 0;
int av;
void loop()
{
if(startPulse)
{
startPulse = false;
/* stabilize the readout display */
if (readCounter == 9)
{
av = analogRead(A0);
readCounter = 0;
}
else
readCounter++;
set_led_pins((av >> 12) & 0xF, D_ONE, false);
delayMicroseconds(250);
set_led_pins((av >> 8) & 0xF, D_TWO, false);
delayMicroseconds(250);
set_led_pins((av >> 4) & 0xF, D_THREE, false);
delayMicroseconds(250);
set_led_pins(av & 0xF, D_FOUR, false);
delayMicroseconds(250);
set_led_pins(BLANK, D_FOUR, false);
}
}
You’ll see there that I am still using the built-in delay-timer functionality to create some delay in-between setting led pins. But the loop itself is initiated by a “pulse” to the startPulse variable which is set by my custom interrupt timer.