Posted on Leave a comment

Baseline ADC Readings Through Serial on AtMega328p

I hope this is legal putting this here. This is modified code from the Make: AVR Programming book. If you wanting to hop from Arduino to C Programming, this book is INCREDIBLE! Seriously, just freakin’ buy it.

This code measures a voltage coming into PC0 and spits it out through the serial monitor. The scaling isn’t right, but adjusting the voltage on PC0 with a potentiometer delivers reasonable results.


// ************ ADC works well enough   
//p.135  AVR Programming Make Book Mostly ********************
// The scaling is screwy, but I can adjust a potentiometer 
//and get reasonable 8-bit data
// ADC is reading PC0.  

#include <avr/io.h>
#include <util/delay.h>
#include "pinDefines.h"
#include "USART.h"

static inline void initADC0(void) {
	ADMUX |= (1 << REFS0);  // reference voltage
	ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // ADC clock prescaler /8
	ADCSRA |= (1 << ADEN);  // enable ADC	
}

int main(void){
	// Inits
	uint16_t adcValue;
	//uint8_t i;
	initADC0();
	initUSART();
	printString("Hello!\r\n");
	
	while (1) {
		ADCSRA |= (1 << ADSC);  // start ADC conversion
		loop_until_bit_is_clear(ADCSRA, ADSC);  
                // wait until finished
		adcValue = ADC; // Read ADC in
		
		_delay_ms(50);
		transmitByte(adcValue);
		_delay_ms(1000);
		
	}  // end big loop
	
	return(0);
}
Posted on Leave a comment

Controlling PWM With Potentiometer On AtMega328p

This is crude, but the thing works.  We’ve all seen this on the Great Scott Youtube channel (among other places) where a potentiometer controls the duty cycle of a PWM waveform on an oscilloscope.  This version isn’t pretty.  It needs some sanding and painting.  However, it’ll get a person started.  I’m adding it here as my personal library to steal from as needed.  Feel free to do the same.

Most of it is also stolen/borrowed from two main sources.

  1.  AVR Programming book by Make  (p.135)
  2.   AVRFreaks.net forum post 

Hardware Requirements

PC0 is looking for an input voltage ( keep it under 5V!!!!).  I made a voltage divider with a 10k resistor and a 10k pot using the AtMega’s 5V supply.  This will max out at 2.5V so I’m definitely throwing resolution away.

AREF needs 5V, too.  I tossed a random cap across it any GND to reduce some of the nasties.

#include <avr/io.h>
#include <util/delay.h>
#include "pinDefines.h"
#include "USART.h"

static inline void initADC0(void) {
	ADMUX |= (1 << REFS0);  // reference voltage
	ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // ADC clock prescaler /8
	ADCSRA |= (1 << ADEN); // enable ADC } uint16_t divider = 1; void Duty( uint8_t percentage, uint16_t ICR1_value) { percentage = (percentage > 100 ? 100 : (percentage < 0 ? 0 : percentage)); uint16_t OCR = (uint16_t)(((uint32_t)percentage * (uint32_t)ICR1_value)/100) ; // Set pwm percent of pwm period OCR1AH = OCR >> 8;
OCR1AL = OCR & 0xFF;
}

void FrequencyPWM(uint16_t frequency, uint8_t percentage)
{
uint16_t TOP = F_CPU/(divider*frequency) - 1;
ICR1H = TOP >> 8;
ICR1L = TOP & 0xFF;
Duty(percentage, TOP);
}

void PWM1_INIT()
{
DDRB |= (1 << PINB1);
//DDRB |= (1 << PINB2);
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 62.500 kHz - I changed this to No prescailing using CS12=0, CS11=0, and CS10 = 1
// Mode: Fast PWM top=ICR1
// OC1A output: Non-Inverted PWM
// OC1B output: Non-Inverted PWM
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer Period: 1 s
// Output Pulse(s):
// OC1A Period: 1 s Width: 0.2 s
// OC1B Period: 1 s Width: 0.40001 s
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=(1<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (1<<WGM11) | (0<<WGM10);
TCCR1B=(0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);
TCNT1H=0x00;
TCNT1L=0x00;

FrequencyPWM(50, 10);
}

int main(void){
	
	PWM1_INIT();
		
	// Inits
	uint16_t adcValue;
	//uint8_t i;
	initADC0();
	initUSART();
	printString("Hello!\r\n");
	
	FrequencyPWM(10000, 50);
	_delay_ms(100);
	
	while(1){
		ADCSRA |= (1 << ADSC);  // start ADC conversion
		loop_until_bit_is_clear(ADCSRA, ADSC);  // wait until finished
		adcValue = ADC; // Read ADC in
		_delay_ms(50);
		transmitByte(adcValue);
		//_delay_ms(1000);
		
		uint8_t scaled_ADC = adcValue / 3;
		FrequencyPWM(10000, scaled_ADC);   // ( frequency , duty cycle % )	
	}	
}
Posted on Leave a comment

Understanding Max7219 LED Driver In C Programming



I’ve slowly been drifting away from Arduino. Even if I was making this transition full-time, it would still be slow. Real deal embedded programming is more like mountain climbing or aging than the usual skill development that happens via a few Youtube videos.

Big Things To Know About Max7219

  • The datasheet tells you most of what you know.
  • To talk to an LED matrix, we need to send an 8-bit address and an 8-bit value. This is actually only implied in the datasheet. It’s not full-blown explicit as the datasheet is more concerned with decoding for 7-segment displays. The addresses are 1-8 and correspond to a row/column (depending on which way you have your matrix turned. The 8-bit value corresponds to the value of that row. 0b11111111 turns on all the LEDs in that row/column. 0b00011000 turns on only the middle 2 leds in the that column.
  • Understanding shift registers is sorta required for cascading multiple matrices, but it turns out that they aren’t all that bad.
  • Cascading multiple led matrices is mention in the datasheet as the “No Op” feature. They mention sending a dummy byte to address 0x00 with data 0x00 to give first Max7219 something to eat and then sending the 0x03 address with 0b11111111 to turn all the leds on the third row of the second matrix.
  • Max7219 shift registers are setup so that that the first 7219 snags the first 2 bytes and passes the rest on. If your intent is to turn on rows 1 and 8 of the first matrix….
    address 0x01 data 0b11111111
    address 0x08 data 0b111111111

Sending the above psudeo code would turn on rows 1 and 8 of the first matrix and turn on row 8 of the second matrix. We need to use those No Op zeros to get what we want.

  • The trick is when to latch. Google “digital latching”. The latching is done when we set the ~SS HIGH after loading up our data.

We can’t send the SPI data to the Max7219 unless the ~SS goes low.
SPI_PORT &= ~(1<<SPI_CS); // ~SS goes LOW

Now ew are talking.

We do our sending

SPDR = address;  // give the SPI data register the address of 7219

while (!(SPSR & (1<<SPIF) ));  // wait for the data to be sent

SPDR = dataout;  // load up SPI data register with dataout

while (!(SPSR & (1<<SPIF)));    // wait for data to be sent.

Now we need to say “goodbye” to end our conversation.

SPI_PORT |= (1<<SPI_CS); // ~SS goes HIGH

This

In the above code, the 8-bit address is sent and then the 8-bit dataout is sent. We do this all in one latching. If we were working 8 matrices, we’d need to set the ~SS low, send the address and dataout 8 times (one for each matrix), and then set the ~SS high to end the conversation.

You’ll see that there is no direct way to communicate with the third Max7219, but we have tricks. We know the first 7219 is going to steal the first 2 bytes. The second 7219 is going to steal the next 2 bytes.

Let’s create an array that’ll handle this.

uint8_t dumb_array[6] ;
dumb_array[0] = 0x00;
dumb_array[1] = 0;
dumb_array[2] = 0x00;
dumb_array[3] = 0;
dumb_array[4] = 0x03;
dumb_array[5] = 0b11111111;

The first Max7219 eats up dumb_array[0] = 0x00 and dumb_array[1] . The array is now only 4 entries long. I looks like this:
dumb_array[0] = 0x00;
dumb_array[1] = 0;
dumb_array[2] = 0x03;
dumb_array[3] = 0b11111111;

This is sent to the seond Max7219. It chews up the first 2 bytes. The array turns into this:
dumb_array[0] = 0x03;
dumb_array[1] = 0b11111111;

This is sent to the 3rd matrix. The matrix likes this. It turns on all the LEDs in the 3rd matrix.