#include <stdio.h>
#include <stdint.h>
#include <esp32-c3_regs.h> // [ANGEPASSTE HEADER-DATEI mit GPIO_ENABLE_W1TS_REG VERWENDEN!!!!!!!!]
#include <calibrate_adc.c>
//----------GLOBALE KONSTANTEN (Bei Bedarf beliebig anpassen!)----------//
#define LEDS 16 // 16 physische LEDS
#define PIXEL_PER_LED 3 // 3 Pixel pro LED
#define PIXEL_ARRAY_SIZE LEDS * PIXEL_PER_LED // 16 * 3 = 48 Pixel
uint8_t pixels[PIXEL_ARRAY_SIZE]; // Globales Array fuer 48 Pixel mit je 8 Bit = 384 Bit
#define MIN_DISTANCE 5 // Minimale Sensordistanz
#define MAX_DISTANCE 50 // Maximale Sensordistanz [SENSORDISTANZ IST HIER EINSTELLBAR!!!!!!!!!!!!!!!!!! ZUSATZAUFGABE???]
#define TOTAL_MEASUREMENTS_FOR_AVERAGE 10 // Anzahl Messungen für einen Mittelwert
#define MAX_DISTANCE_DEVIATION_PERCENT 20 // +/- Maximale Abweichung in Prozent
//----------INITIALISIERUNG UND GLOBALE FUNKTIONEN----------//
void gpio_init()
{
*GPIO_ENABLE_W1TS_REG |= (0b1 << 0); // Enable GPIO0 als Output (Trigger)
*GPIO_ENABLE_W1TS_REG |= (0b1 << 2); // Enable GPIO2 als Output (LED)
*GPIO_ENABLE_W1TS_REG |= (0b1 << 8); // Enable GPIO8 als Output (LED)
*IO_MUX_GPIO1_REG |= (0b1 << 9); // Enable GPIO1 als Input (Echo)
*IO_MUX_GPIO3_REG |= (0b1 << 9); // Enable GPIO3 als Input (Poti)
}
void adc_init()
{
*APB_SARADC_CTRL_REG |= (0b1 << 0); // ADC per Software Variante auswählen
*APB_SARADC_CTRL_REG |= (0b1 << 1); // ADC per Software starten
*APB_SARADC_ONETIME_SAMPLE_REG |= (0b11 << 23); // Dämpfung auf 12dB
*APB_SARADC_ONETIME_SAMPLE_REG &= ~(0b1111 << 25);// Vorige Kanalauswahl löschen
*APB_SARADC_ONETIME_SAMPLE_REG |= (3 << 25); // Kanal 3 auswählen
*APB_SARADC_ONETIME_SAMPLE_REG |= (0b1 << 31); // Enable OneTimeSampling ADC1
*APB_SARADC_INT_ENA_REG |= (0b1 << 31); // Enable Done-Interrupt ADC1
adc_calibrate(1); // Kalibriere ADC1
}
void timer_init()
{
*TIMG_T0CONFIG_REG &= ~(0b1 << 9); // APB_CLK nutzen
*TIMG_T0CONFIG_REG |= (80 << 13); // Divider setzen (f = 80MHz / 80 = 1MHz ==> T = 1us)
*TIMG_T0CONFIG_REG |= (0b1 << 30); // Increase Counter
}
void reset_timer()
{
*TIMG_T0LOADLO_REG |= (0b0 <<0); // Startzeit = 0 (untere 32 Bit)
*TIMG_T0LOADHI_REG |= (0b0 <<0); // Startzeit = 0 (obere 22 Bit)
*TIMG_T0LOAD_REG |= (0b0 <<0); // Startzeit laden
}
void delay_ns(unsigned int ns) // Delay in Nanosekunden (1 Takt = 43ns)
{
asm volatile(
//Berechnung der Taktanzahl
"li t1, 43 \n\t" // t1 = 43 (1 Takt = 43ns)
"li t0, 21 \n\t" // t0 = 43/2 = 21 (Rundung)
"add t0, t0, %0 \n\t" // t0 = 21 + xns (Rundung)
"div t0, t0, t1 \n\t" // t0 = (21 + xns) / 43 (Takte gerundet)
"addi t0, t0, -5 \n\t" // t0 = Takte - 5 (nop-Takte ohne Initialisierung)
//NOP-Schleife
"1: \n\t"
"nop \n\t"
"addi t0, t0, -1 \n\t" // t0--
"bnez t0, 1b \n\t" // while(t0 != 0) -> 1:
//Parameter
: // Ausgabe: -
: "r" (ns) // Eingabe: Nanosekunden in a0
: "t0", "t1" // Clobber-Register
);
}
//----------LEDS----------//
void write_one(int gpio)
{
*GPIO_OUT_W1TS_REG |= (0b1 << gpio); // GPIO2 setzen
delay_ns(600); // 600ns warten
*GPIO_OUT_W1TC_REG |= (0b1 << gpio); // GPIO2 ruecksetzen
delay_ns(600); // 600ns warten
}
void write_zero(int gpio)
{
*GPIO_OUT_W1TS_REG |= (0b1 << gpio); // GPIO2 setzen
delay_ns(300); // 300ns warten
*GPIO_OUT_W1TC_REG |= (0b1 << gpio); // GPIO2 ruecksetzen
delay_ns(900); // 900ns warten
}
void display_pixelsArray_LEDstripe()
{
for (int i = 0; i < PIXEL_ARRAY_SIZE; i++) // Fuer alle 48 Pixel
{
for (int k = 7; k >=0 ; k--) // Fuer alle 8 Bit jedes Pixels
{
if (pixels[i] & (0b1 << k)) // Jedes Bit pruefen
{
write_one(2); // Bei 1: Schreib 1
}
else
{
write_zero(2); // Bei 0: Schreib 0
}
}
}
delay_ns(50000); // 50us warten damit die LEDs die Daten uebernehmen (siehe Datenblatt)
}
void display_pixelsArray_onboardLED()
{
for (int i = 0; i < 3; i++) // Fuer alle 48 Pixel
{
for (int k = 7; k >=0 ; k--) // Fuer alle 8 Bit jedes Pixels
{
if (pixels[i] & (0b1 << k)) // Jedes Bit pruefen
{
write_one(8); // Bei 1: Schreib 1
}
else
{
write_zero(8); // Bei 0: Schreib 0
}
}
}
delay_ns(50000); // 50us warten damit die LEDs die Daten uebernehmen (siehe Datenblatt)
}
//----------ULTRASCHALLSENSOR----------//
int read_distance_cm()
{
int distanceLow32 = 0;
reset_timer(); // Timer auf 0s
// Trigger
*GPIO_OUT_W1TS_REG |= (0b1 << 0); // GPIO0 (Trigger) setzen
delay_ns(11000); // 11us warten
*GPIO_OUT_W1TC_REG |= (0b1 << 0); // GPIO0 (Trigger) ruecksetzen
// Echo + Timer
while (!(*GPIO_IN_REG &= (0b1 << 1))){} // Warten bis Echo Signal kommt
*TIMG_T0CONFIG_REG |= (0b1 << 31); // TIMER STARTEN
while (*GPIO_IN_REG &= (0b1 << 1)) {} // Warten solange Echo-Signal da ist
*TIMG_T0CONFIG_REG &= ~(0b1 << 31); // TIMER STOPPEN
// Timer auslesen
*TIMG_T0UPDATE_REG |= (0b1 << 0); // Timerwertausgabe beauftragen
while (*TIMG_T0UPDATE_REG &= (0b1 << 1)) {} // Warten bis Timerwert da ist
distanceLow32 = *TIMG_T0LO_REG; // untere 32 Bit des Timers auslesen
//(obere 22 Bit werden nicht benötigt, da (2^32)/16 = 268s, Echo ist jedoch maximal 38ms lang)
// Timerwert verarbeiten
int distanceCm = distanceLow32 / 58; // Umrechnung in cm (siehe Datenblatt des HC-SR04)
if(distanceCm < 2 || distanceCm > 400)
{
return -1;
}
else
{
return distanceCm;
}
}
int calc_averageDistanceCorrected()
{
int distancesArray[TOTAL_MEASUREMENTS_FOR_AVERAGE]; // Array für Mittelwert
int averageDistance = 0; // Mittelwert
int averageDistanceCorrected = 0; // Mittelwert ohne Ausreisser
// Berechnung des Mittelwerts
for(int i = 0; i< TOTAL_MEASUREMENTS_FOR_AVERAGE; i++)
{
distancesArray[i] = read_distance_cm(); // Entfernungsmessung starten und Abstand in cm in Array auslesen
if(distancesArray[i] == -1)
{
distancesArray[i] = distancesArray[i-1];
}
averageDistance += distancesArray[i]; // Alle Werte aufsummieren
//delay_ns(10000);
}
averageDistance = averageDistance / TOTAL_MEASUREMENTS_FOR_AVERAGE; // Mittelwert bilden
// Entfernung abweichender Werte und erneute Bildung des Mittelwerts
int floorDistanceDeviation = averageDistance - (averageDistance * MAX_DISTANCE_DEVIATION_PERCENT / 100); // Untere minimale Abweichung
int ceilDistanceDeviation = averageDistance + (averageDistance * MAX_DISTANCE_DEVIATION_PERCENT / 100); // Obere maximale Abweichung
for(int i = 0; i< TOTAL_MEASUREMENTS_FOR_AVERAGE; i++)
{
if(distancesArray[i] < floorDistanceDeviation || distancesArray[i] > ceilDistanceDeviation)
{
distancesArray[i] = averageDistance; // Falls Abweichung zu groß, wird der Wert auf Mittelwert gesetzt
}
averageDistanceCorrected += distancesArray[i];
}
averageDistanceCorrected = averageDistanceCorrected / TOTAL_MEASUREMENTS_FOR_AVERAGE; // Mittelwert der korrigierten Messwerte bilden
return averageDistanceCorrected;
}
//----------POTENTIOMETER----------//
int adc_read()
{
*APB_SARADC_INT_CLR_REG |= (0b1 << 31); // Done-Bit Clearen
*APB_SARADC_ONETIME_SAMPLE_REG |= (0b1 << 29); // One-Time Sampling starten
delay_ns(500000);
*APB_SARADC_ONETIME_SAMPLE_REG &= ~(0b1 << 29); // One-Time Sampling stoppen
while(!(*APB_SARADC_INT_RAW_REG & (0b1 << 31) >> 31)){} // Warten bis Konvertierung abgeschlossen
return (*APB_SARADC_1_DATA_STATUS_REG &= (0xFFF)); // Digitalwert auslesen
}
//----------LOOP----------//
void fill_pixelsArray(int distance, int maxBrightness)
{
int range = MAX_DISTANCE - MIN_DISTANCE;
int Red_Value = ((distance * 100 / 50) * maxBrightness / 100);
int Green_Value = maxBrightness - ((distance * 100 / 50) * maxBrightness / 100);
int Blue_Value = 0;
for (int i = 0; i < PIXEL_ARRAY_SIZE; i++)
{
pixels[i] = 0; // Initialisiere alle Pixel auf 0
}
if (distance > MAX_DISTANCE) // Bei d > MAX_DISTANCE bleiben alle Pixel aus
{
return;
}
else if (distance <= MIN_DISTANCE) // Bei d<= 5 werden alle Pixel maximal hell
{
for (int i = 0; i < PIXEL_ARRAY_SIZE; i+=3)
{
pixels[i] = 0;
pixels[i + 1] = maxBrightness;
pixels[i + 2] = 0;
}
}
else // Bei MIN_DISTANCE < d <= MAX_DISTANCE
{
// Berechnung der Pixel der aktiven LEDs
int scaledDistance = (distance - MIN_DISTANCE) * maxBrightness / range; // Skaliere d auf 0 bis maxBrightness
int fullActivePixels = (maxBrightness - scaledDistance) * LEDS / maxBrightness; // Anzahl vollständiger LEDs
int partActivePixelsBrightness = (maxBrightness - scaledDistance) * LEDS % maxBrightness; // Wert der letzten LED
// Alle Pixel der vollständigen LEDs aktivieren
for (int i = 0; i < fullActivePixels * PIXEL_PER_LED; i += 3)
{
pixels[i] = Red_Value;
pixels[i + 1] = Green_Value;
pixels[i + 2] = Blue_Value;
}
// Die letzte LED mit dem berechneten Wert setzen
if (fullActivePixels < LEDS)
{
int partActivePixelsBrightnessValue;
if (partActivePixelsBrightness > maxBrightness) // Abfangen zu großer Werte
{
partActivePixelsBrightnessValue = maxBrightness;
}
else
{
partActivePixelsBrightnessValue = partActivePixelsBrightness;
}
Red_Value = ((distance * 100 / 50) * partActivePixelsBrightnessValue / 100);
Green_Value = partActivePixelsBrightnessValue - ((distance * 100 / 50) * partActivePixelsBrightnessValue / 100);
Blue_Value = 0;
pixels[fullActivePixels * PIXEL_PER_LED] = Red_Value;
pixels[fullActivePixels * PIXEL_PER_LED + 1] = Green_Value;
pixels[fullActivePixels * PIXEL_PER_LED + 2] = Blue_Value;
}
}
}
//----------MAIN----------//
void app_main(void)
{
gpio_init();
adc_init();
timer_init();
while (1)
{
int adcValue = adc_read(); // ADC Wert (0...4095)
int maxPixelBrightness = (adcValue * 255) / 4095; // Helligkeitswert der Pixel (0...255)
int averageDistanceCorrected = calc_averageDistanceCorrected(); // Korigierter Mittelwert der Distanz
fill_pixelsArray(averageDistanceCorrected, maxPixelBrightness); // Pixels-Array mit Helligkeitswerten belegen
display_pixelsArray_LEDstripe(); // Pixels-Array in LEDs ausgeben
display_pixelsArray_onboardLED();
delay_ns(100000); // 0,1s = 100ms = 100.000us = 100.000.000ns [WIE LANGE SOLLEN WIR HIER WARTEN ?????????????]
/*
// DEBUGGING: Ausgabe aller Pixelwerte nach LEDs
for (int i = 0; i < PIXEL_ARRAY_SIZE; i++)
{
printf("%d ", pixels[i]);
if ((i + 1) % PIXEL_PER_LED == 0) printf("| "); // Gruppierung in LEDs
}
printf("\n");
*/
}
}
/*
//----------BACKUP----------//
LED cm | LED1 | LED2 | LED3 ... | LED16
0 53 0 0 0 0 0 0 0 0 0 0 0 0
1 50 255 255 255 0 0 0 0 0 0 0 0 0
2 47 255 255 255 255 255 255 0 0 0 0 0 0
3 44 255 255 255 255 255 255 255 255 255 0 0 0
4 41 . . . .
5 38 . . . .
6 35 . . . .
7 32 . . . .
8 29 . . . .
9 26 . . . .
10 23 . . . .
11 20 . . . .
12 17 . . . .
13 14 . . . .
14 11 . . . .
15 8 . . . .
16 5 255 255 255 255 255 255 255 255 255 255 255 255
int activePixels = calc_activePixels(averageDistanceCorrected); // Aktive Pixel berechnen
printf("ActivePixels: %d | ", activePixels);
int calc_activePixels(int cm)
{
int activePixels = 0;
if(cm > 53) // Maximaler Grenzfall
{
activePixels = 0;
}
else if(cm <= 5) // Minimaler Grenzfall
{
activePixels = 48;
}
else // Fuer 50 >= cm > 5
{
activePixels = 3 * (17 - (cm / 3));
}
return activePixels;
}
for (int i = 0; i < PIXEL_ARRAY_SIZE; i++) // Alle Pixel initial auf 0
{
pixels[i] = 0;
}
for (int i = 0; i < activePixels && i < PIXEL_ARRAY_SIZE; i++) // Aktive Pixel mit Maximalwerten befuellen, Rest bleibt 0
{
pixels[i] = pixelBrightness;
}
int sensorDistance_cm = readSensor_cm(); // Entfernungsmessung starten und Abstand in cm auslesen
printf("Abstand: %dcm\n", sensorDistance_cm);
void write_zero()
{
asm volatile(
// 1 schreiben
"li t1, 4 \n\t" // t1 = 2
"lw t2, %0 \n\t" // t2 = GPIO_OUT_REG
"or t3, t2, t1 \n\t" // GPIO2 setzen
"sw t3, %0 \n\t" // GPIO2 = 1 rausschreiben
// 300ns warten
delay_ns(300);
// 0 schreiben
"li t1, 4 \n\t" // t1 = 2 == 0b0000 0010
"lw t2, %0 \n\t" // t2 = GPIO_OUT_REG
"not t1, t1 \n\t" // ~0b1111 1101
"and t3, t2, t1 \n\t" // Verunden
"sw t3, %0 \n\t" // GPIO2 = 0 rausschreiben
// 950ns warten
delay_ns(950);
:
: "m" (*GPIO_OUT_REG) // ÄNDERN ZU: GPIO_OUT_W1TS_REG => Wird in der Doku empfohlen! => TESTEN!!! Dann wird die 0 mit GPIO_OUT_W1TC_REG geschrieben => ASSEMBLER ANPASSEN!!
: "t0", "t1", "t2", "t3"
);
}
void write_one()
{
asm volatile(
// 1 schreiben
"li t1, 4 \n\t" //2 laden
"lw t2, %0 \n\t" //GPIO_OUT Adresse laden
"or t3, t2, t1 \n\t" //Bit 2 setzen(GPIO2)
"sw t3, %0 \n\t" //Adresse rausschreiben
// 96 Takte = 600ns warten
"addi t0, zero, 14 \n\t" // t3 = 96 (NOPs für 600ns)
"1: \n\t" // NOP-Schleife für 600ns
"nop \n\t" // NOP
"addi t0, t0, -1 \n\t" // Decrement t3
"bnez t0, 1b \n\t" // Wiederhole, solange t3 != 0
// 0 schreiben
"li t1, 4 \n\t" //2 laden
"lw t2, %0 \n\t" //GPIO_OUT Adresse laden
"not t1, t1 \n\t" // Tilde
"and t3, t2, t1 \n\t" // Verunden
"sw t3, %0 \n\t" //Adresse rausschreiben
// 104 Takte = 650ns warten
"addi t0, zero, 15 \n\t" // t3 = 104 (NOPs für 650ns)
"2: \n\t" // NOP-Schleife für 650ns
"nop \n\t" // NOP
"addi t0, t0, -1 \n\t" // Decrement t3
"bnez t0, 2b" // Wiederhole, solange t3 != 0
:
: "m" (*GPIO_OUT_REG) // ÄNDERN ZU: GPIO_OUT_W1TS_REG => Wird in der Doku empfohlen! => TESTEN!!! Dann wird die 0 mit GPIO_OUT_W1TC_REG geschrieben => ASSEMBLER ANPASSEN!!
: "t0", "t1", "t2", "t3"
);
}
void delay_us(unsigned int xus) { // Delay in Mikrosekunden (1 Takt = 43ns) // KANN WEG!!!!!!!!!!!!!!!!!
asm volatile(
//----Berechnung der Takteanzahl = xus / 43ns----
"li t1, 43 \n\t" // t1 = 43
"li t2, 1000 \n\t" // t2 = 1000
"mul t2, %0, t2 \n\t" // t2 = us * 1000 -> Zeit in ns
"div t2, t2, t1 \n\t" // t2 = (us * 1000) / 43 -> Takte
"addi t2, t2, -6 \n\t" // t2 = Takte - 6 -> nop-Takte ohne Initialisierung
"addi t0, t2, 0 \n\t" // Lade die Anzahl der Takte in t0
//----NOP-Schleife----
"1: \n\t"
"nop \n\t"
"addi t0, t0, -1 \n\t" // t0--
"bnez t0, 1b \n\t" // while(t0 != 0) -> 1:
//----Parameter----
: // Ausgabe:
: "r" (xus) // Eingabe: Mikrosekunden in a0
: "t0", "t1", "t2" // Clobber-Register
);
}
void period_trigger()
{
GPIO_OUT_REG |= (0b1 << 0);
asm volatile( // 233 Takte = 50us warten
"addi t0, zero, 233 \n\t" // t3 = 96 (NOPs für 600ns)
"1: \n\t" // NOP-Schleife für 600ns
"nop \n\t" // NOP
"addi t0, t0, -1 \n\t" // Decrement t3
"bnez t0, 1b \n\t" // Wiederhole, solange t3 != 0
:
:
: "t0"
);
GPIO_OUT_REG &= ~(0b1 << 0);
asm volatile( // 2100 Takte = 50us warten
"addi t0, zero, 210 \n\t" // t3 = 96 (NOPs für 600ns)
"1: \n\t" // NOP-Schleife für 600ns
"nop \n\t" // NOP
"addi t0, t0, -1 \n\t" // Decrement t3
"bnez t0, 1b \n\t" // Wiederhole, solange t3 != 0
:
:
: "t0"
);
}
void period_trigger()
{
*GPIO_OUT_W1TS_REG |= (0b1 << 0);
delay_ns(50000);
*GPIO_OUT_W1TC_REG |= (0b1 << 0);
delay_ns(50000);
}
*/