// Libraries
#undef F_CPU
#define F_CPU 8000000UL
#include <avr/interrupt.h>
#include <avr/io.h> // for GPIO
#include <avr/pgmspace.h> // to store data in programm memory
#include <avr/delay.h>
// Pin definitions PODE TRABALAHR COM QUALQUER OUTRO PINS
#define I2C_SCL PB2 // I2C serial clock pin
#define I2C_SDA PB0 // I2C serial data pin
/******************************/
/*PROGRAM VARIABLES AND VALUES*/
/******************************/
#define time_delay_to_accept 100 //ms // set time to use on "wait_ms(xxx)" on "static uint8_t incremet(uint8_t Value_to_increment)"
#define n_time_delay_to_accept 9 // number of times to count "wait_ms(xxxx);" time on "static uint8_t incremet(uint8_t Value_to_increment)"
#define erro 1.016 //1.022 <- Bons resultados com 1.022 //1.0274; // More info at end of code: secction ERROR
volatile uint32_t timer=0; // interrupts counter
volatile uint32_t time_save=0; // save "timer" value = 1 period time
volatile uint32_t time_calc=0; // time TO calculate rpms, incremented with "time_save"
volatile bool period_complete = false; // is a new period ? if YES calculate in main function
volatile uint8_t n_targets_1_rotation=0; // How many targets one rpm as ?
volatile uint8_t targets=0; // count targets in one rotation
volatile float rpm=0; // store rpm calculation
volatile uint8_t multiplicador=1; // number to multiply with rpm: Tacometer sees one Target, sensor see 2 targuets, sensor see 2x ActualRpm
/******************************/
/* GPIO */
/******************************/
#define PB4_input() ( DDRB &= ~(1 << DDB4) ) // INPUT for SENSOR
#define PB4_pullup() ( PORTB |= (1 << PB4) )
#define PB3_input() ( DDRB &= ~(1 << DDB3) ) // INPUT for BUTTON
#define PB3_read() ((PINB & (1 << PB3)) == 8) // por = high -> PB3_read() = true if( PB3_read() )
#define PB4_read() ((PINB & (1 << PB4)) == 16)
// ===================================================================================
// I2C Implementation
// ===================================================================================
// I2C macros
#define I2C_SDA_HIGH() DDRB &= ~(1<<I2C_SDA) // release SDA -> pulled HIGH by resistor
#define I2C_SDA_LOW() DDRB |= (1<<I2C_SDA) // SDA as output -> pulled LOW by MCU
#define I2C_SCL_HIGH() DDRB &= ~(1<<I2C_SCL) // release SCL -> pulled HIGH by resistor
#define I2C_SCL_LOW() DDRB |= (1<<I2C_SCL) // SCL as output -> pulled LOW by MCU
// I2C init function
void I2C_init(void) {
DDRB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // pins as input (HIGH-Z) -> lines released
PORTB &= ~((1<<I2C_SDA)|(1<<I2C_SCL)); // should be LOW when as ouput
}
// I2C transmit one data byte to the slave, ignore ACK bit, no clock stretching allowed
void I2C_write(uint8_t data) {
for(uint8_t i = 8; i; i--, data<<=1) { // transmit 8 bits, MSB first
I2C_SDA_LOW(); // SDA LOW for now (saves some flash this way)
if(data & 0x80) I2C_SDA_HIGH(); // SDA HIGH if bit is 1
I2C_SCL_HIGH(); // clock HIGH -> slave reads the bit
I2C_SCL_LOW(); // clock LOW again
}
I2C_SDA_HIGH(); // release SDA for ACK bit of slave
I2C_SCL_HIGH(); // 9th clock pulse is for the ACK bit
I2C_SCL_LOW(); // but ACK bit is ignored
}
// I2C start transmission
void I2C_start(uint8_t addr) {
I2C_SDA_LOW(); // start condition: SDA goes LOW first
I2C_SCL_LOW(); // start condition: SCL goes LOW second
I2C_write(addr); // send slave address
}
// I2C stop transmission
void I2C_stop(void) {
I2C_SDA_LOW(); // prepare SDA for LOW to HIGH transition
I2C_SCL_HIGH(); // stop condition: SCL goes HIGH first
I2C_SDA_HIGH(); // stop condition: SDA goes HIGH second
}
// ===================================================================================
// OLED Implementation
// ===================================================================================
// OLED definitions
#define OLED_ADDR 0x78 // OLED write address
#define OLED_CMD_MODE 0x00 // set command mode
#define OLED_DAT_MODE 0x40 // set data mode
#define OLED_INIT_LEN 15 // length of OLED init command array
// OLED init settings
const uint8_t OLED_INIT_CMD[] PROGMEM = {
0xA8, 0x1F, // set multiplex for 128x32
0x22, 0x00, 0x03, // set min and max page
0x20, 0x01, // set vertical memory addressing mode
0xDA, 0x02, // set COM pins hardware configuration to sequential
0x8D, 0x14, // enable charge pump
0xAF, // switch on OLED
0x00, 0x10, 0xB0 // set cursor at home position
};
/*
Se o numero zero começar na linha 100 é mais facil de saber a posição dos caracteres
http://dotmatrixtool.com/#
*/
const uint8_t OLED_FONT[] PROGMEM = {
// C1 , C2 , C3 começa de baixo para cima
// primeira linha de baixo é 0
0x7F, 0x41, 0x7F, // 0
0x04, 0x7f, 0x00, // 0x00, 0x00, 0x7F, // 1
0x79, 0x49, 0x4F, // 2
0x41, 0x49, 0x7F, // 3
0x0F, 0x08, 0x7E, // 4
0x4F, 0x49, 0x79, // 5
0x7F, 0x49, 0x79, // 6
0x03, 0x01, 0x7F, // 7
0x7F, 0x49, 0x7F, // 8
0x4F, 0x49, 0x7F, // 9
0x00, 0x00, 0x00, // clear
0x7F, 0x7F, 0x7F, // blank
0x7F, 0x09, 0x7F, // A
0x7f, 0x49, 0x36, // B
0x7F, 0x48, 0x78, // b
0x7F, 0x41, 0x63, // C
0x78, 0x48, 0x48, // c
0x7f, 0x22, 0x1c, // D
0x78, 0x48, 0x7F, // d
0x7F, 0x49, 0x41, // E
0x7F, 0x09, 0x01, // F
0b01010111, 0b01110000, 0b00010000, //G
0x6e, 0x4a, 0x3e, //g
0x7f, 0x08, 0x7f, //H
0x41, 0x7f, 0x41, //I
0x00, 0x7a, 0x00, //i
0x70, 0x40, 0x7f, //J
0x7f, 0x14, 0x62, //k
0x7f, 0x40, 0x40, // L
0x48, 0x78, 0x40, //l
0x7f, 0x04, 0x7f, // M
0x00, 0x00, 0x00, // fazer N
0x7F, 0x41, 0x7F, // 0
0x7f, 0x09, 0x06, // P
0x1e, 0x21, 0x5e, // Q
0x7f, 0x09, 0x66, // R
0x46, 0x49, 0x31, // S
0x01, 0x7f, 0x01, // T
0x7f, 0x40, 0x7f, // U
0x78, 0x40, 0x78, // u
0x1f, 0x40, 0x1f, // V
0x7F, 0x20, 0x7F, // W
0x63, 0x08, 0x63, // X
0x03, 0x7c, 0x03, // Y
0x61, 0x49, 0x43, // Z
0x00, 0x60, 0x00, // .
0x00, 0x36, 0x00, // :
0x08, 0x08, 0x08, // -
0x20, 0x70, 0x20, // +
0x50, 0x20, 0x50, // x
0x06, 0x18, 0x60, // /
0x60, 0x18, 0x06, // "\"
0x00, 0x7f, 0x00, // |
0x22, 0x14, 0x08, // >
0x08, 0x14, 0x22, // <
0x14, 0x14, 0x14 //=
};
//A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z.
// OLED global variables
uint8_t buffer[8] = {10, 10, 10, 10, 10, 10, 10, 10}; // screen buffer
// OLED init function
void OLED_init(void) {
I2C_init(); // initialize I2C first
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_CMD_MODE); // set command mode
for(uint8_t i = 0; i < OLED_INIT_LEN; i++) I2C_write(pgm_read_byte(&OLED_INIT_CMD[i])); // send the command bytes
I2C_stop(); // stop transmission
}
// OLED stretch a part of a byte
uint8_t OLED_stretch(uint8_t b) {
b = ((b & 2) << 3) | (b & 1); // split 2 LSB into the nibbles
b |= b << 1; // double the bits
b |= b << 2; // double them again = 4 times
return b; // return the value
}
// OLED print a big digit by stretching the character
void OLED_printD(uint8_t ch) {
uint8_t i, j, k, b; // loop variables
uint8_t sb[4]; // stretched character bytes
ch += ch << 1; // calculate position of character in font array
for(i=8; i; i--) I2C_write(0x00); // print spacing between characters
for(i=3; i; i--) { // font has 3 bytes per character
b = pgm_read_byte(&OLED_FONT[ch++]); // read character byte
for(j=0; j<4; j++, b >>= 2) sb[j] = OLED_stretch(b); // stretch 4 times
j=4; if(i==2) j=6; // calculate x-stretch value
while(j--) { // write several times (x-direction)
for(k=0; k<4; k++) I2C_write(sb[k]);// the 4 stretched bytes (y-direction)
}
}
}
// OLED print buffer
void OLED_printB(uint8_t *buffer) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
for(uint8_t i=0; i<8; i++) OLED_printD(buffer[i]); // print buffer
I2C_stop(); // stop transmission
}
// ===================================================================================
// LPT_ functions:
// ===================================================================================
// set parameter in buffer[]
void buffer_clean(uint8_t number_of_char){
for(uint8_t i=0; i<8; i++) buffer[i]=number_of_char; //reset the buffer
}
void buffer_values_align_to_right (const uint16_t val){
uint16_t aux_val=val; // uses the val of the variable
uint8_t n_digits=0; // used to know number of digits to ALIGNT to RIGHT
//buffer_clean(10);
// calculate n digits of VAl
/* do {
aux_val /= 10;
++n_digits;
} while (aux_val != 0); */
// calculate n digits of VAl
while (aux_val != 0){
aux_val /= 10;
++n_digits;
}
aux_val=val;
uint8_t aling_number= n_digits;
uint8_t aux = 8-n_digits;
//Right allingment
// place number is buffer : 1= OLED_FONT[1] ea ssim diante
//https://stackoverflow.com/questions/9302681/c-how-to-break-apart-a-multi-digit-number-into-separate-variables
// https://qnaplus.com/c-program-get-digit-position-number/
while (n_digits--) {
buffer[aux+n_digits]=aux_val%10; // 1º buffer [7], 2º buffer[6]...
aux_val/=10;
}
while (aling_number--) {
//buffer[aling_number]=10;
}
}
void buffer_Add_2digit_val_At_index_i_plus_ONE( uint8_t two_digit_val, uint8_t start_index){
buffer[start_index]= ( (two_digit_val % 100)/10);
buffer[start_index+1]= (two_digit_val % 10);
}
void buffer_Add_3_letters_At_index_x( uint8_t l0, uint8_t l1,uint8_t l2, uint8_t index){
buffer[index]= l0;
buffer[index+1]= l1;
buffer[index+2]= l2;
}
void buffer_Add_1_letter_At_index( uint8_t i0, uint8_t l0){
buffer[i0]= l0;
}
/******************************/
/* INTERRUPT TIMER */
/******************************/
// INTERRUPT SERVICE ROUTINE
ISR (TIMER0_COMPA_vect){ //Interrupt vector for Timer0
// depends of F_CPU , TCCR0B_pre_scaler and OCR0A_value
timer++;
}
void timer0_comp_match_Setup(){
TCCR0A=0x00; //Normal mode
TCCR0B=0x00;
TCCR0B |= (1<<CS01)|(1<<CS00) ; // pres-scaler=64
TCCR0A |=(1<<WGM01); // Toggle mode and compare match mode
OCR0A = 125 ; // OCR0A – Output Compare Register A // 8MHz + pres-scaler=64 (0.008ms) + OCR0A_value=125 -> 125*0.008 = 1ms
// 11.9.4 TCNT0 – Timer/Counter Register
TCNT0 = 0;
sei(); // enabling global interrupt
// 11.9.7 TIMSK – Timer/Counter Interrupt Mask Register
TIMSK|=(1<<OCIE0A); // Bit 4 – OCIE0A: Timer/Counter0 Output Compare Match A Interrupt Enable
}
/******************************/
/* INTERRUPT PIN CHANGE */
/******************************/
ISR (PCINT0_vect){ // Interrupt service routine for Pin Change Interrupt Request 0 -PIN is set on function : pin_change_interrupt_setup()
if(PB4_read() && timer > 5){ // timer > x - try to use without relay
time_save=timer; // save timer value
timer=0; // clean timer
TCNT0=0; // clean TCNT0
period_complete=true; // when true we have Toff+Ton = 1 period
}
}
void pin_change_interrupt_setup() {
//cli(); // Disable interrupts during setup
// 9.3.2 GIMSK – General Interrupt Mask Register
GIMSK|= (1<<PCIE); // pin change interrupt is enabled.
// 9.3.4 PCMSK – Pin Change Mask Register
//PCMSK|= (1<<PCINT1); // PCINT1=PB1
//PCMSK|= (1<<PCINT2); // PCINT2=PB2 NAO SEI SE dá para usar em simultaneo com o INT
//PCMSK|= (1<<PCINT3); // PCINT1=PB3
PCMSK|= (1<<PCINT4); // PCINT1=PB4
//PCMSK|= (1<<PCINT5); // PCINT1=PB5
//sei(); //enabling global interrupt
}
/******************************/
/* PROGRAM FUNCTIONS */
/******************************/
// same as _delay_ms() from "delay.h" . dont know if is precise, in this use doens't matter -> just saved Flash
static void wait_ms(uint16_t time_to_wait){
//In his function, the varable "timer" (incremented in ISR() ) is set to ZERO and this fucntion set it to ZERO.
// the ISR continues to increment it and when the while function does tthe delay: do until
timer = 0; // set interrupts counter to zero
TCNT0 = 0; // clean TCNT0
while (time_to_wait > timer) { } // executa enquanto "time_to_wait" for maior que "timer"
}
void wait_for_button_and_delay_time (uint16_t time){
while( PB4_read() ){};
wait_ms(time);
}
static uint8_t incremet(uint8_t Value_to_increment, uint8_t index_for_value){
//012345667
//tg:_12_12
bool block=false; // User most relise botton and press it again to increment. Acts like a flipflop
uint8_t aux=n_time_delay_to_accept;
uint8_t btt=0;
while(aux--){ // Executa enquanto "aux" for menor ou igual que "n_time_delay_to_accept"
btt= (PB3_read()) ? true : false; // tenary: (PB3_read()) == true : ; this value need to be stored because 1 button action is used 2 times.
if(block==false && btt==true){
Value_to_increment++;
block=true;
aux=n_time_delay_to_accept;
}
(Value_to_increment <=9)? buffer_Add_1_letter_At_index(index_for_value,Value_to_increment) : buffer_Add_2digit_val_At_index_i_plus_ONE(Value_to_increment, index_for_value);
buffer_Add_1_letter_At_index(7,aux);
//buffer_Add_2digit_val_At_index_i_plus_ONE(aux, 7);
OLED_printB(buffer);
if(btt==0) block=false; // reset "flipflop"
wait_ms(time_delay_to_accept);
}
return Value_to_increment;
}
static void wellcome(){
buffer_clean(10);
//buffer_Add_3_letters_At_index_x(28,33,37,0); // "LPT: "
OLED_printB(buffer);
wait_ms(50);
buffer_clean(10);
buffer_Add_3_letters_At_index_x(28,10,10,0); // "LPT: "
OLED_printB(buffer);
wait_ms(200);
buffer_clean(10);
buffer_Add_3_letters_At_index_x(10,33,10,0); // "LPT: "
OLED_printB(buffer);
wait_ms(200);
buffer_clean(10);
buffer_Add_3_letters_At_index_x(28,33,37,0); // "LPT: "
OLED_printB(buffer);
wait_ms(200);
}
// ===================================================================================
// Main Function
// ===================================================================================
// Main function
int main(void) {
OLED_init(); // initialize the OLED
timer0_comp_match_Setup(); // wait_ms uses this timer
wellcome();
buffer_clean(10);
buffer_Add_3_letters_At_index_x(37,22,46,0); // "Tg: "
n_targets_1_rotation = incremet(1,4);
//wait_ms(200);
buffer_clean(10);
buffer_Add_3_letters_At_index_x(35,33,30,0); // "RPM"
buffer_Add_1_letter_At_index(3,49); //"x"
multiplicador = incremet(1,4);
pin_change_interrupt_setup();
uint8_t menu=0;
bool btt = false;
bool block=false;
uint8_t reset_counter=0;
while(true){
if(period_complete){ // prevent doing the same account over and over again
targets++;
time_calc+=time_save;
if(n_targets_1_rotation <= targets){
//rpm= (erro*60000)/(time_calc*OCR0A_value_ms);
rpm= (erro*60000)/(time_calc);
time_calc=0;
targets=0;
if(menu==2){
buffer_Add_1_letter_At_index(5,55); //">"
OLED_printB(buffer);
}
}
period_complete=false; // clean completed period
}
btt= (PB3_read()) ? true : false;
while(PB3_read()){
_delay_ms(10);
reset_counter++;
btt=true;
if(reset_counter >= 254){
timer=0;
time_calc=0;
targets=0;
rpm=0;
buffer_clean(10);
buffer_Add_3_letters_At_index_x(35,19,37,0); // "RST"
buffer_Add_3_letters_At_index_x(49,49,49,4); // "xxx"++
OLED_printB(buffer);
menu=0;
btt=false;
while(PB3_read()){}
break;
}
}
if(btt == true & block==false){
//menu++;
menu=(menu >= 4) ? 0 : menu+1 ; // menu=(menu >= 4) ? 0 : menu++ ; devia de dar
block=true;
}
if(timer>=6200){
timer=0;
menu=3;
}
if(btt==0) block=false; // reset "flipflop"
switch(menu){
case 0: //rpm
buffer_clean(10);
buffer_Add_3_letters_At_index_x(35,33,30,0); // "RPM"
buffer_values_align_to_right(multiplicador * rpm);
OLED_printB(buffer);
wait_for_button_and_delay_time(500);
break;
case 1: // targerts;
buffer_clean(10);
buffer_Add_3_letters_At_index_x(37,22,46,0); // "Tg: "
(targets <=9)? buffer_Add_1_letter_At_index(4,targets) : buffer_Add_2digit_val_At_index_i_plus_ONE(targets, 3);
buffer_Add_1_letter_At_index(5,54); //"=""
if(targets==0){ // ADD animação para quamdo faz uma volta
// buffer_Add_1_letter_At_index(5,55); //">"
}
buffer_values_align_to_right(n_targets_1_rotation);
OLED_printB(buffer);
break;
case 2: // Time;
buffer_clean(10);
buffer_Add_3_letters_At_index_x(37,46,10,0); // "T: "
buffer_values_align_to_right(time_calc);
OLED_printB(buffer);
break;
case 3: // LESS 1RPM;
buffer_clean(10);
buffer_Add_3_letters_At_index_x(28,19,5,0); // "LES"
buffer_Add_3_letters_At_index_x(5,1,1,3); // "S 1"
buffer_Add_3_letters_At_index_x(35,33,30,5); // "RPM"
buffer_clean(10);
buffer_Add_3_letters_At_index_x(35,33,30,0); // "RPM"
buffer_Add_3_letters_At_index_x(10,54,10,3); // " < "
buffer_Add_1_letter_At_index(7,1);
OLED_printB(buffer);
break;
default:
menu=0;
break;
}
}
return 1;
}