/*
https://wokwi.com/projects/343769763897410131
diagram.json:
{
"version": 1,
"author": "lpt",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-attiny85", "id": "tiny", "top": 160, "left": 47, "attrs": {} },
{ "type": "board-ssd1306", "id": "ssd1306", "top": 260.56, "left": 4.29, "attrs": {} },
{
"type": "wokwi-pushbutton",
"id": "btn1",
"top": 192.56,
"left": 144.28,
"attrs": { "color": "red" }
},
{
"type": "wokwi-resistor",
"id": "r1",
"top": 250.65,
"left": 136.76,
"attrs": { "value": "1000" }
}
],
"connections": [
[ "tiny:GND", "ssd1306:GND", "black", [ "v8", "*", "h0" ] ],
[ "tiny:VCC", "ssd1306:VCC", "red", [ "v-4", "h-12", "v52", "*", "h0" ] ],
[ "ssd1306:SCL", "tiny:PB3", "green", [ "v0" ] ],
[ "ssd1306:SDA", "tiny:PB4", "green", [ "v0" ] ],
[ "tiny:VCC", "btn1:1.l", "red", [ "v-36.43", "h5.45" ] ],
[ "tiny:PB0", "btn1:2.l", "green", [ "h32.98", "v64.38" ] ],
[ "tiny:GND", "r1:1", "black", [ "v0" ] ],
[ "r1:2", "btn1:2.r", "green", [ "v0" ] ]
]
}
*/
// ===================================================================================
// Project: TinyTacho - RPM-Meter using ATtiny13A and I²C OLED
// Version: v1.0
// Year: 2020
// Author: Stefan Wagner
// Github: https://github.com/wagiminator
// EasyEDA: https://easyeda.com/wagiminator
// License: http://creativecommons.org/licenses/by-sa/3.0/
// ===================================================================================
/* Edited by LPT :
+ Utiliza muito menos RAM e flash
- Apenas dá para escrever um buffer UNICO de 8 caracteres, Não dá a para apresentgar texto e numeros separadamente.
( talvez dê para "martelar" o buffer:)
On Attiny85-20pu
RAM: [ ] 3.1% (used 16 bytes from 512 bytes)
Flash: [= ] 7.4% (used 608 bytes from 8192 bytes)
*/
// Libraries
#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 PB3 // I2C serial clock pin
#define I2C_SDA PB4 // I2C serial data pin
#define PB0_read() ((PINB & (1 << PB0)) != 1 ) // high = true
// ===================================================================================
// 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 20 // 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
};
// OLED simple reduced 3x8 font
// Nesta fonte, a ultima linha é sempre 0
/*
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
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, // <
};
//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
/*
uint8_t slow[8] = {10, 10, 5, 10, 0, 11, 12, 10}; // "SLOW"
uint16_t divider[5] = {10000, 1000, 100, 10, 1}; // for BCD conversion
uint8_t test[8] = {23, 22, 21, 20, 24, 11, 12, 12}; // "SLOW"
uint8_t clear[8] = {10, 10, 10, 10, 10, 10, 10, 10};
uint8_t black[8] = {11, 11, 11, 11, 11, 11, 11, 11};
uint8_t numbers0_5[8] = {0,1,2,3,4,10,10,10};
uint8_t numbers5_9[8] = {5,6,7,8,9,10,10,10};
uint8_t letters_1[8] = {12,13,14,15,16,17,18,19};
uint8_t letters_2[8] = {20,21,22,23,24,25,26,27};
uint8_t letters_3[8] = {28,29,30,31,32,33,34,35};
uint8_t letters_4[8] = {36,37,38,39,40,41,42,43};
uint8_t letters_5[8] = {44,45,46,47,48,49,50,51};
*/
// 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
}
/*
// OLED print 16 bit value (BCD conversion by substraction method)
void OLED_printW_old(uint16_t value) {
for(uint8_t digit = 0; digit < 5; digit++) { // 5 digits
uint8_t digitval = 0; // start with digit value 0
while(value >= divider[digit]) { // if current divider fits into the value
digitval++; // increase digit value
value -= divider[digit]; // decrease value by divider
}
buffer[digit + 1] = digitval; // set the digit in the screen buffer
}
OLED_printB(buffer); // print screen buffer on the OLED
}
void OLED_printW(uint16_t value) {
uint8_t n_digits=0;
uint8_t numeros_direta=0;
uint16_t valor=value;
for(uint8_t digit = 0; digit < 5; digit++) { // 5 digits
uint8_t digitval = 0; // start with digit value 0
while(value >= divider[digit]) { // if current divider fits into the value
digitval++; // increase digit value
value -= divider[digit]; // decrease value by divider
}
// om estes TENARY, não mete digitos zero á esquerda do numero:
digitval = ( valor < 9999 && digit == 0 ) ? 10 : digitval;
digitval = ( valor < 999 && digit <= 1 ) ? 10 : digitval;
digitval = ( valor < 99 && digit <= 2 ) ? 10 : digitval;
digitval = ( valor < 9 && digit <= 3 ) ? 10 : digitval;
numeros_direta = (valor <= 9)? 6 : numeros_direta;
numeros_direta = (valor <= 99)? 5: numeros_direta;
numeros_direta = (valor <= 999)? 4: numeros_direta;
numeros_direta = (valor <= 9999)? 3 : numeros_direta;
numeros_direta = (valor >= 9999)? 2 : numeros_direta;
buffer[digit + numeros_direta] = digitval; // set the digit in the screen buffer
}
OLED_printB(buffer);
}*/
// ===================================================================================
// 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_3_letters_At_index_012( uint8_t l0, uint8_t l1,uint8_t l2){
buffer[0]= l0;
buffer[1]= l1;
buffer[2]= l2;
}
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_1_letter_At_index( uint8_t i0, uint8_t l0){
buffer[i0]= l0;
}
void wait_for_button_and_delay_time (uint16_t time){
while( PB0_read() ){};
_delay_ms(500);
}
// ===================================================================================
// Main Function
// ===================================================================================
// Main function
int main(void) {
OLED_init(); // initialize the OLED
uint16_t rpm=1235;
buffer_clean(10);
buffer_values_align_to_right(rpm);
buffer_Add_3_letters_At_index_012(35,33,30);
OLED_printB(buffer);
wait_for_button_and_delay_time(500);
rpm=1;
buffer_clean(10);
buffer_values_align_to_right(rpm);
buffer_Add_3_letters_At_index_012(35,33,30); // "RPM"
OLED_printB(buffer);
wait_for_button_and_delay_time(500);
rpm=62501;
buffer_clean(10);
buffer_values_align_to_right(rpm);
buffer_Add_3_letters_At_index_012(37,46,10); // "T: "
OLED_printB(buffer);
wait_for_button_and_delay_time(500);
buffer_clean(10);
uint8_t alvos =12;
buffer_values_align_to_right(10);
buffer_Add_3_letters_At_index_012(37,22,46); // "Tg: "
buffer_Add_2digit_val_At_index_i_plus_ONE(alvos, 3);
// buffer_Add_1_letter_At_index(3,(alvos % 100)/10);
//buffer_Add_1_letter_At_index(4,alvos % 10);
buffer_Add_1_letter_At_index(5,53);
OLED_printB(buffer);
wait_for_button_and_delay_time(500);
/*uint16_t rpm=54;
uint8_t n_numbers=0;
n_numbers = (rpm <= 9)? 1 : n_numbers;
n_numbers = (rpm <= 99)? 2: n_numbers;
n_numbers = (rpm <= 999)? 3: n_numbers;
n_numbers = (rpm <= 9999)? 4 : n_numbers;
n_numbers = (rpm >= 9999)? 5 : n_numbers;
uint8_t aux = 8-n_numbers;
while (n_numbers--) {
buffer[aux+n_numbers]=rpm%10;
rpm/=10;
}
OLED_printB(buffer);
uint16_t rpm=12345;
buffer[3]= (num % 100000)/10000; //T
buffer[4]= (num % 10000)/1000; //:
buffer[5]= (num % 1000)/100; //:
buffer[6]= (num % 100)/10; //:
buffer[7]= (num % 10); //:
OLED_printB(buffer);
while( PB0_read() ){};
OLED_printB(clear);
// Buffer é so de 8 caracteres:
//12345678
//RPM45678
//T:345678
//Tg:12/12
buffer[0]= 35; //R
buffer[1]= 33; //P
buffer[2]= 30; //M
OLED_printW(rpm);
while( PB0_read() ){};
OLED_printB(clear);
_delay_ms(500);
buffer[0]= 37; //T
buffer[1]= 46; //:
buffer[3]= 10; //:
OLED_printW(rpm);
while( PB0_read() ){};
_delay_ms(500);
buffer[0]= 37; //T
buffer[1]= 22; //g
buffer[2]= 46; //:
buffer[3]= 1; //1
buffer[4]= 2; //2
buffer[5]= 53; //
buffer[6]= 1; //1
buffer[7]= 2; //2
OLED_printB(buffer);
while( PB0_read() ){};
_delay_ms(1000);
OLED_printB(letters_1);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(letters_2);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(letters_3);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(letters_4);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(letters_5);
while(PB0_read() ){};
_delay_ms(1000);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(numbers0_5);
while(PB0_read() ){};
_delay_ms(1000);
OLED_printB(black);
while( PB0_read() ){};
_delay_ms(1000);
*/
};