Ajoute la liaison TMS320 (simulee), l'IHM OLED et la LED de statut

Protocole ASCII type NMEA ($T/$C avec checksum) entre l'ESP32-C3 et le
TMS320 (frequence/duty/tensions/courants/temperatures + commandes
HT/PWM1/PWM2), avec simulateur local le temps que le firmware TMS320
existe. Driver SSD1322 repointe sur les GPIO du schema, dashboard texte
3 pages tournantes sur l'ecran ER-OLEDM032-1. LED RGB embarquee (GPIO8)
clignotante 1 Hz vert 30% comme indicateur de vie. Serveur web laisse en
stub (sera branche en STA sur le reseau local une fois l'OLED valide).
Ajout de la documentation technique (schema, datasheets, protocole).
This commit is contained in:
2026-08-01 15:24:59 +02:00
parent 9cd046f65c
commit df3621365c
20 changed files with 2224 additions and 0 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
desktop.ini
_desktop.ini

View File

@ -0,0 +1,76 @@
/***************************************************
//Web: http://www.buydisplay.com
EastRising Technology Co.,LTD
Examples for ER-OLEDM032-1
Display is Hardward SPI 4-Wire SPI Interface
Tested and worked with:
Works with Arduino 2.3.4 IDE
NOTE: test OK:ESP32-WROOM-32
****************************************************/
/*
Note:The module needs to be jumpered to an SPI interface. R19,R23 Short and R18,R20 Open
Unused signal pin Recommended to connect to GND
/* == Hardward SPI to ESP32 ==
OLED => ESP32
*1. GND -> GND
*2. VCC -> 3.3-5V
*4. SCL -> 18
*5. SDI -> 23
*7-13 -> GND
*14. DC -> 17
*15. RES -> 16
*16. CS -> 5
*/
#include <SPI.h>
#include "er_oled.h"
//uint8_t oled_buf[OLED_Y_MAXPIXEL * OLED_X_MAXPIXEL/2];
void setup() {
Serial.begin(9600);
Serial.print("OLED Example\n");
/* display an image of bitmap matrix */
er_oled_begin();
}
void loop() {
er_oled_clear();
er_oled_bitmap_gray(PIC1);
delay(3000);
er_oled_clear();
er_oled_bitmap_gray(PIC2);
delay(3000);
er_oled_clear();
er_oled_bitmap_mono(PIC3);
delay(3000);
er_oled_clear();
er_oled_string(0, 0, "********************************",0);
er_oled_string(32, 16, "EastRising Technology", 0);
er_oled_string(40, 32, "www.buydisplay.com", 1);
er_oled_string(0, 48, "********************************",0);
uint8_t i;
for(i=0;i<=48;i++)
{
command(0xa1); //start line
data(i);
delay(100);
}
for(i=48;i>0;i--)
{
command(0xa1); //start line
data(i);
delay(100);
}
command(0xa1); //start line
data(0);
}

View File

@ -0,0 +1,187 @@
/***************************************************
//Web: http://www.buydisplay.com
EastRising Technology Co.,LTD
****************************************************/
#include <SPI.h>
#include "er_oled.h"
void command(uint8_t cmd)
{
digitalWrite(OLED_DC, LOW);
digitalWrite(OLED_CS, LOW);
SPI.transfer(cmd);
digitalWrite(OLED_CS, HIGH);
}
void data(uint8_t dat)
{
digitalWrite(OLED_DC, HIGH);
digitalWrite(OLED_CS, LOW );
SPI.transfer(dat);
digitalWrite(OLED_CS, HIGH);
}
void er_oled_begin()
{
pinMode(OLED_RST, OUTPUT);
pinMode(OLED_DC, OUTPUT);
pinMode(OLED_CS, OUTPUT);
SPI.begin();
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
//SPI.setClockDivider(SPI_CLOCK_DIV2);
digitalWrite(OLED_CS, LOW);
digitalWrite(OLED_RST, HIGH);
delay(10);
digitalWrite(OLED_RST, LOW);
delay(10);
digitalWrite(OLED_RST, HIGH);
command(0xFD); /*SET COMMAND LOCK*/
data(0x12); /* UNLOCK */
command(0xAE); /*DISPLAY OFF*/
command(0xB3);/*DISPLAYDIVIDE CLOCKRADIO/OSCILLATAR FREQUANCY*/
data(0x91); command(0xCA); /*multiplex ratio*/
data(0x3F); /*duty = 1/64*/
command(0xA2); /*set offset*/
data(0x00);
command(0xA1); /*start line*/
data(0x00);
command(0xA0); /*set remap*/
data(0x14);
data(0x11);
command(0xAB); /*funtion selection*/
data(0x01); /* selection external vdd */
command(0xB4); /* */
data(0xA0);
data(0xfd);
command(0xC1); /*set contrast current */
data(0x80);
command(0xC7); /*master contrast current control*/
data(0x0f);
command(0xB1); /*SET PHASE LENGTH*/
data(0xE2);
command(0xD1); /**/
data(0x82);
data(0x20);
command(0xBB); /*SET PRE-CHANGE VOLTAGE*/
data(0x1F);
command(0xB6); /*SET SECOND PRE-CHARGE PERIOD*/
data(0x08);
command(0xBE); /* SET VCOMH */
data(0x07);
command(0xA6); /*normal display*/
command(0xAF); /*display ON*/
}
void er_oled_SetWindow(uint8_t Xstart, uint8_t Ystart, uint8_t Xend, uint8_t Yend)
{
command(0x15);
data(Xstart+0x1c);
data(Xend+0x1c);
command(0x75);
data(Ystart);
data(Yend);
command(0x5c);//write ram command
}
void er_oled_clear()
{int i,row;
command(0x15);
data(0x00); //col start
data(0x77); //col end
command(0x75);
data(0x00); //row start
data(0x7f); //row end
command(0x5c);
for (row = 0; row < 128; row++) {
for(i = 0; i< 240; i++ ) {
data(0x00);// write data
}
}
}
void er_oled_char(uint8_t x, uint8_t y, const char *acsii, uint8_t mode)
{ uint8_t i,str;uint16_t OffSet;
x=x/4;
OffSet = (*acsii - 32)*16;
er_oled_SetWindow(x, y, x+1, y+15);
for (i=0;i<16;i++)
{ str =pgm_read_byte(&AsciiLib[OffSet + i]);
if(mode) str=~str;
Data_processing (str);
}
}
void er_oled_string(uint8_t x, uint8_t y, const char *pString, uint8_t Mode)
{
while(1)
{
if (*pString == 0)
{
return;
}
er_oled_char(x, y, pString,Mode);
x += 8;
pString += 1;
}
}
void Data_processing(uint8_t temp) //turns 1byte B/W data to 4 bye gray data with 8 Pixel
{uint8_t temp1,temp2;
if(temp&0x80)temp1=0xf0;
else temp1=0x00;
if(temp&0x40)temp2=0x0f;
else temp2=0x00;
temp1=temp1|temp2;
data(temp1); //Pixel1,Pixel2
if(temp&0x20)temp1=0xf0;
else temp1=0x00;
if(temp&0x10)temp2=0x0f;
else temp2=0x00;
temp1=temp1|temp2;
data(temp1); //Pixel3,Pixel4
if(temp&0x08)temp1=0xf0;
else temp1=0x00;
if(temp&0x04)temp2=0x0f;
else temp2=0x00;
temp1=temp1|temp2;
data(temp1); //Pixel5,Pixel6
if(temp&0x02)temp1=0xf0;
else temp1=0x00;
if(temp&0x01)temp2=0x0f;
else temp2=0x00;
temp1=temp1|temp2;
data(temp1); //Pixel7,Pixel8
}
void er_oled_bitmap_mono(const uint8_t * pBuf)
{ uint8_t row,col,dat;
er_oled_SetWindow(0, 0, 255/4, 63);
for (row = 0; row < 64; row++) {
for(col = 0;col<256/8; col++ ) {
dat=(pgm_read_byte(pBuf));
*pBuf++;
Data_processing(dat);
}
}
}
void er_oled_bitmap_gray(const uint8_t * pBuf)
{ uint8_t row,col;
er_oled_SetWindow(0, 0, 255/4, 63);
for (row = 0; row < 64; row++) {
for(col = 0;col<128; col++ ) {
data(pgm_read_byte(pBuf));
* pBuf++;
}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

BIN
doc/SSD1322.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
Paramètre TMS320:
Fréquence, Duty cycle (a convertir en %) etage 1 etage 2
A partir de lectures ADC:
Tension entrée, courant d'entrée
Tension etage 1, courant shunt mosfet 1, température (NTC) etage1, tension étage 2, courant shunt mosfet 2, température etage2.
Courant sortie.
Commande: activer/desactiver. sortie HT
activer désactiver PWM1
activer/desactiver PWM2.

BIN
doc/schema-esp32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

24
include/config.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
// Liaison UART vers le TMS320
#define PIN_TMS_UART_TX 7
#define PIN_TMS_UART_RX 6
#define TMS_UART_BAUD 115200
// LED RGB embarquee (WS2812) de l'ESP32-C3-DevKitM-1
#define PIN_RGB_LED 8
// Ecran OLED ER-OLEDM032-1 (SSD1322, SPI 4 fils)
#define PIN_OLED_MOSI 5
#define PIN_OLED_CLK 4
#define PIN_OLED_CS 10
#define PIN_OLED_RST 3
#define PIN_OLED_DC 1
// Le firmware TMS320 n'est pas encore programme : on simule la telemetrie
// en local le temps que le hardware TMS320 tourne. Passer a false quand le
// TMS320 emet reellement des trames $T,...*XX sur l'UART.
#define TMS_DUMMY_MODE true
// WiFi/serveur web de l'IHM : a definir une fois l'affichage OLED valide
// (connexion STA au reseau local, SSID/mot de passe a fournir).

38
include/protocol.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <Arduino.h>
// Protocole ASCII de type NMEA entre l'ESP32-C3 et le TMS320.
//
// Telemetrie TMS320 -> ESP32, periodique :
// $T,FREQ1=100000,FREQ2=100000,DUTY1=45.2,DUTY2=50.0,VIN=400.5,IIN=1.20,
// V1=200.3,I1=2.50,T1=45.2,VOUT=200.1,I2=2.48,T2=44.8,IOUT=1.05*XX\n
//
// Commande ESP32 -> TMS320, a chaque changement (et rafraichissement
// periodique) :
// $C,HT=1,PWM1=1,PWM2=0*XX\n
//
// XX = XOR de tous les octets entre '$' et '*', en hexadecimal majuscule.
struct Telemetry {
float freq1 = 0; // Hz, frequence etage 1
float freq2 = 0; // Hz, frequence etage 2
float duty1 = 0; // %, duty cycle etage 1
float duty2 = 0; // %, duty cycle etage 2
float vin = 0; // V, tension entree
float iin = 0; // A, courant entree
float v1 = 0; // V, tension etage 1
float i1 = 0; // A, courant shunt mosfet 1
float t1 = 0; // C, temperature NTC etage 1
float vout = 0; // V, tension etage 2 (sortie boost)
float i2 = 0; // A, courant shunt mosfet 2
float t2 = 0; // C, temperature NTC etage 2
float iout = 0; // A, courant sortie
uint32_t lastUpdateMs = 0;
};
struct CommandState {
bool ht = false; // sortie HT activee/desactivee
bool pwm1 = false; // PWM etage 1 activee/desactivee
bool pwm2 = false; // PWM etage 2 activee/desactivee
};

225
src/er_oled.cpp Normal file
View File

@ -0,0 +1,225 @@
// Adapte du tutoriel EastRising Technology (buydisplay.com) pour
// ER-OLEDM032-1 / SSD1322, repointe sur les broches SPI de l'ESP32-C3
// (config.h) et limite a l'affichage texte.
#include "er_oled.h"
#include "config.h"
#include <SPI.h>
static const uint8_t AsciiLib[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* */
0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x18,0x18,0x00,0x00, /* ! */
0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x28,0x28,0x50,0x00,0x00,0x00,0x00,0x00,0x00, /* " */
0x00,0x00,0x00,0x14,0x14,0x7E,0x14,0x14,0x28,0x7E,0x28,0x28,0x28,0x00,0x00,0x00, /* # */
0x00,0x00,0x10,0x38,0x54,0x54,0x50,0x30,0x18,0x14,0x54,0x54,0x38,0x10,0x10,0x00, /* $ */
0x00,0x00,0x00,0x44,0xA4,0xA8,0xA8,0x50,0x14,0x2A,0x2A,0x4A,0x44,0x00,0x00,0x00, /* % */
0x00,0x00,0x00,0x20,0x50,0x50,0x50,0x7C,0xA8,0xA8,0x98,0x88,0x76,0x00,0x00,0x00, /* & */
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x08,0x30,0x00,0x00,0x00,0x00,0x00,0x00, /* ' */
0x00,0x00,0x04,0x08,0x10,0x10,0x20,0x20,0x20,0x20,0x20,0x10,0x10,0x08,0x04,0x00, /* ( */
0x00,0x00,0x20,0x10,0x08,0x08,0x04,0x04,0x04,0x04,0x04,0x08,0x08,0x10,0x20,0x00, /* ) */
0x00,0x00,0x00,0x00,0x10,0x10,0xD6,0x38,0x38,0xD6,0x10,0x10,0x00,0x00,0x00,0x00, /* * */
0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0xFE,0x10,0x10,0x10,0x00,0x00,0x00,0x00, /* + */
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x08,0x30,0x00,0x00,0x00,0x00,0x00,0x00, /* , */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* . */
0x00,0x00,0x02,0x04,0x04,0x04,0x08,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40,0x00, /* / */
0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,0x00, /* 0 */
0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00, /* 1 */
0x00,0x00,0x00,0x3C,0x42,0x42,0x02,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,0x00, /* 2 */
0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x42,0x42,0x3C,0x00,0x00,0x00, /* 3 */
0x00,0x00,0x00,0x08,0x08,0x18,0x28,0x48,0x48,0x7E,0x08,0x08,0x1E,0x00,0x00,0x00, /* 4 */
0x00,0x00,0x00,0x7E,0x40,0x40,0x5C,0x62,0x02,0x02,0x42,0x42,0x3C,0x00,0x00,0x00, /* 5 */
0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x3C,0x00,0x00,0x00, /* 6 */
0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00, /* 7 */
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x3C,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,0x00, /* 8 */
0x00,0x00,0x00,0x38,0x44,0x42,0x42,0x46,0x3A,0x02,0x02,0x24,0x38,0x00,0x00,0x00, /* 9 */
0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00, /* : */
0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10,0x00,0x00,0x00, /* ; */
0x00,0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x02,0x00,0x00, /* < */
0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, /* = */
0x00,0x00,0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x40,0x00,0x00, /* > */
0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x02,0x04,0x08,0x08,0x00,0x18,0x18,0x00,0x00, /* ? */
0x00,0x00,0x00,0x38,0x44,0x9A,0xAA,0xAA,0xAA,0xAA,0xB4,0x42,0x3C,0x00,0x00,0x00, /* @ */
0x00,0x00,0x00,0x10,0x10,0x28,0x28,0x28,0x28,0x7C,0x44,0x44,0xEE,0x00,0x00,0x00, /* A */
0x00,0x00,0x00,0xFC,0x42,0x42,0x44,0x78,0x44,0x42,0x42,0x42,0xFC,0x00,0x00,0x00, /* B */
0x00,0x00,0x00,0x3E,0x42,0x82,0x80,0x80,0x80,0x80,0x82,0x44,0x38,0x00,0x00,0x00, /* C */
0x00,0x00,0x00,0xF8,0x44,0x42,0x42,0x42,0x42,0x42,0x42,0x44,0xF8,0x00,0x00,0x00, /* D */
0x00,0x00,0x00,0xFC,0x42,0x48,0x48,0x78,0x48,0x48,0x40,0x42,0xFC,0x00,0x00,0x00, /* E */
0x00,0x00,0x00,0xFC,0x42,0x48,0x48,0x78,0x48,0x48,0x40,0x40,0xE0,0x00,0x00,0x00, /* F */
0x00,0x00,0x00,0x3C,0x44,0x84,0x80,0x80,0x80,0x8E,0x84,0x44,0x38,0x00,0x00,0x00, /* G */
0x00,0x00,0x00,0xEE,0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44,0xEE,0x00,0x00,0x00, /* H */
0x00,0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00, /* I */
0x00,0x00,0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x88,0xF0,0x00,0x00, /* J */
0x00,0x00,0x00,0xEE,0x44,0x48,0x50,0x70,0x50,0x48,0x48,0x44,0xEE,0x00,0x00,0x00, /* K */
0x00,0x00,0x00,0xE0,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x42,0xFE,0x00,0x00,0x00, /* L */
0x00,0x00,0x00,0xEE,0x6C,0x6C,0x6C,0x54,0x54,0x54,0x54,0x54,0xD6,0x00,0x00,0x00, /* M */
0x00,0x00,0x00,0xEE,0x64,0x64,0x54,0x54,0x54,0x4C,0x4C,0x4C,0xE4,0x00,0x00,0x00, /* N */
0x00,0x00,0x00,0x38,0x44,0x82,0x82,0x82,0x82,0x82,0x82,0x44,0x38,0x00,0x00,0x00, /* O */
0x00,0x00,0x00,0xFC,0x42,0x42,0x42,0x7C,0x40,0x40,0x40,0x40,0xE0,0x00,0x00,0x00, /* P */
0x00,0x00,0x00,0x38,0x44,0x82,0x82,0x82,0x82,0x82,0xB2,0x4C,0x38,0x06,0x00,0x00, /* Q */
0x00,0x00,0x00,0xF8,0x44,0x44,0x44,0x78,0x50,0x48,0x48,0x44,0xE6,0x00,0x00,0x00, /* R */
0x00,0x00,0x00,0x3E,0x42,0x42,0x40,0x30,0x0C,0x02,0x42,0x42,0x7C,0x00,0x00,0x00, /* S */
0x00,0x00,0x00,0xFE,0x92,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x38,0x00,0x00,0x00, /* T */
0x00,0x00,0x00,0xEE,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00,0x00, /* U */
0x00,0x00,0x00,0xEE,0x44,0x44,0x44,0x28,0x28,0x28,0x28,0x10,0x10,0x00,0x00,0x00, /* V */
0x00,0x00,0x00,0xD6,0x54,0x54,0x54,0x54,0x6C,0x28,0x28,0x28,0x28,0x00,0x00,0x00, /* W */
0x00,0x00,0x00,0xEE,0x44,0x28,0x28,0x10,0x10,0x28,0x28,0x44,0xEE,0x00,0x00,0x00, /* X */
0x00,0x00,0x00,0xEE,0x44,0x44,0x28,0x28,0x10,0x10,0x10,0x10,0x38,0x00,0x00,0x00, /* Y */
0x00,0x00,0x00,0x3E,0x44,0x04,0x08,0x08,0x10,0x10,0x20,0x22,0x7E,0x00,0x00,0x00, /* Z */
0x00,0x00,0x3C,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3C,0x00, /* [ */
0x00,0x00,0x40,0x40,0x20,0x20,0x10,0x10,0x10,0x08,0x08,0x04,0x04,0x04,0x02,0x00, /* \ */
0x00,0x00,0x3C,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3C,0x00, /* ] */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* _ */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ` */
0x00,0x00,0x00,0x00,0x00,0x38,0x44,0x1C,0x24,0x44,0x44,0x3E,0x00,0x00,0x00,0x00, /* a */
0x00,0x00,0x00,0xC0,0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x42,0x7C,0x00,0x00,0x00, /* b */
0x00,0x00,0x00,0x00,0x00,0x1C,0x22,0x40,0x40,0x40,0x22,0x1C,0x00,0x00,0x00,0x00, /* c */
0x00,0x00,0x00,0x0C,0x04,0x04,0x7C,0x84,0x84,0x84,0x84,0x8C,0x76,0x00,0x00,0x00, /* d */
0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x7E,0x40,0x40,0x42,0x3C,0x00,0x00,0x00,0x00, /* e */
0x00,0x00,0x00,0x0E,0x12,0x10,0x7C,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00, /* f */
0x00,0x00,0x00,0x00,0x3E,0x44,0x44,0x38,0x40,0x3C,0x42,0x42,0x3C,0x00,0x00,0x00, /* g */
0x00,0x00,0x00,0xC0,0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x42,0xE7,0x00,0x00,0x00, /* h */
0x00,0x00,0x00,0x30,0x30,0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00, /* i */
0x00,0x00,0x0C,0x0C,0x00,0x1C,0x04,0x04,0x04,0x04,0x04,0x04,0x44,0x78,0x00,0x00, /* j */
0x00,0x00,0x00,0xC0,0x40,0x40,0x4E,0x48,0x50,0x68,0x48,0x44,0xEE,0x00,0x00,0x00, /* k */
0x00,0x00,0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00, /* l */
0x00,0x00,0x00,0x00,0x00,0xF8,0x54,0x54,0x54,0x54,0x54,0xD6,0x00,0x00,0x00,0x00, /* m */
0x00,0x00,0x00,0x00,0x00,0xDC,0x62,0x42,0x42,0x42,0x42,0xE7,0x00,0x00,0x00,0x00, /* n */
0x00,0x00,0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x24,0x18,0x00,0x00,0x00,0x00, /* o */
0x00,0x00,0x00,0x00,0xDC,0x62,0x42,0x42,0x42,0x42,0x7C,0x40,0xE0,0x00,0x00,0x00, /* p */
0x00,0x00,0x00,0x00,0x7C,0x84,0x84,0x84,0x84,0x8C,0x74,0x04,0x0E,0x00,0x00,0x00, /* q */
0x00,0x00,0x00,0x00,0x00,0xEE,0x32,0x20,0x20,0x20,0x20,0xF8,0x00,0x00,0x00,0x00, /* r */
0x00,0x00,0x00,0x00,0x00,0x3C,0x44,0x40,0x38,0x04,0x44,0x78,0x00,0x00,0x00,0x00, /* s */
0x00,0x00,0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x10,0x10,0x10,0x0C,0x00,0x00,0x00, /* t */
0x00,0x00,0x00,0x00,0x00,0xC6,0x42,0x42,0x42,0x42,0x46,0x3B,0x00,0x00,0x00,0x00, /* u */
0x00,0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x28,0x10,0x10,0x00,0x00,0x00,0x00, /* v */
0x00,0x00,0x00,0x00,0x00,0xD6,0x54,0x54,0x54,0x28,0x28,0x28,0x00,0x00,0x00,0x00, /* w */
0x00,0x00,0x00,0x00,0x00,0x6E,0x24,0x18,0x18,0x18,0x24,0x76,0x00,0x00,0x00,0x00, /* x */
0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x28,0x18,0x10,0x10,0xE0,0x00,0x00,0x00, /* y */
0x00,0x00,0x00,0x00,0x00,0x7E,0x44,0x08,0x10,0x10,0x22,0x7E,0x00,0x00,0x00,0x00, /* z */
};
static void oled_command(uint8_t cmd) {
digitalWrite(PIN_OLED_DC, LOW);
digitalWrite(PIN_OLED_CS, LOW);
SPI.transfer(cmd);
digitalWrite(PIN_OLED_CS, HIGH);
}
static void oled_data(uint8_t dat) {
digitalWrite(PIN_OLED_DC, HIGH);
digitalWrite(PIN_OLED_CS, LOW);
SPI.transfer(dat);
digitalWrite(PIN_OLED_CS, HIGH);
}
// Convertit 1 octet noir/blanc (8 pixels) en 4 octets de niveaux de gris
// (2 pixels par octet, format SSD1322 4 bits/pixel).
static void data_processing(uint8_t temp) {
uint8_t hi, lo;
hi = (temp & 0x80) ? 0xF0 : 0x00;
lo = (temp & 0x40) ? 0x0F : 0x00;
oled_data(hi | lo);
hi = (temp & 0x20) ? 0xF0 : 0x00;
lo = (temp & 0x10) ? 0x0F : 0x00;
oled_data(hi | lo);
hi = (temp & 0x08) ? 0xF0 : 0x00;
lo = (temp & 0x04) ? 0x0F : 0x00;
oled_data(hi | lo);
hi = (temp & 0x02) ? 0xF0 : 0x00;
lo = (temp & 0x01) ? 0x0F : 0x00;
oled_data(hi | lo);
}
void er_oled_SetWindow(uint8_t Xstart, uint8_t Ystart, uint8_t Xend, uint8_t Yend) {
oled_command(0x15);
oled_data(Xstart + 0x1C);
oled_data(Xend + 0x1C);
oled_command(0x75);
oled_data(Ystart);
oled_data(Yend);
oled_command(0x5C); // write RAM
}
void er_oled_clear() {
er_oled_SetWindow(0x00, 0x00, 0x77, 0x7F);
for (int row = 0; row < 128; row++) {
for (int i = 0; i < 240; i++) {
oled_data(0x00);
}
}
}
void er_oled_char(uint8_t x, uint8_t y, const char *ascii, uint8_t mode) {
x = x / 4;
uint16_t offset = (*ascii - 32) * 16;
er_oled_SetWindow(x, y, x + 1, y + 15);
for (uint8_t i = 0; i < 16; i++) {
uint8_t b = pgm_read_byte(&AsciiLib[offset + i]);
if (mode) b = ~b;
data_processing(b);
}
}
void er_oled_string(uint8_t x, uint8_t y, const char *pString, uint8_t mode) {
while (*pString != 0) {
er_oled_char(x, y, pString, mode);
x += 8;
pString += 1;
}
}
void er_oled_begin() {
pinMode(PIN_OLED_RST, OUTPUT);
pinMode(PIN_OLED_DC, OUTPUT);
pinMode(PIN_OLED_CS, OUTPUT);
// Broches SPI personnalisees : sck, miso (non cable), mosi, ss
SPI.begin(PIN_OLED_CLK, -1, PIN_OLED_MOSI, PIN_OLED_CS);
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_OLED_CS, LOW);
digitalWrite(PIN_OLED_RST, HIGH);
delay(10);
digitalWrite(PIN_OLED_RST, LOW);
delay(10);
digitalWrite(PIN_OLED_RST, HIGH);
oled_command(0xFD); // set command lock
oled_data(0x12); // unlock
oled_command(0xAE); // display off
oled_command(0xB3); // clock divider / osc frequency
oled_data(0x91);
oled_command(0xCA); // multiplex ratio
oled_data(0x3F); // duty = 1/64
oled_command(0xA2); // display offset
oled_data(0x00);
oled_command(0xA1); // start line
oled_data(0x00);
oled_command(0xA0); // set remap
oled_data(0x14);
oled_data(0x11);
oled_command(0xAB); // function selection
oled_data(0x01); // external VDD
oled_command(0xB4);
oled_data(0xA0);
oled_data(0xFD);
oled_command(0xC1); // contrast current
oled_data(0x80);
oled_command(0xC7); // master contrast current control
oled_data(0x0F);
oled_command(0xB1); // phase length
oled_data(0xE2);
oled_command(0xD1);
oled_data(0x82);
oled_data(0x20);
oled_command(0xBB); // pre-charge voltage
oled_data(0x1F);
oled_command(0xB6); // second pre-charge period
oled_data(0x08);
oled_command(0xBE); // VCOMH
oled_data(0x07);
oled_command(0xA6); // normal display
oled_command(0xAF); // display on
}

18
src/er_oled.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
// Driver bas niveau pour ecran ER-OLEDM032-1 (controleur SSD1322, 256x64,
// SPI 4 fils). Adapte du tutoriel constructeur (buydisplay.com) pour
// utiliser les broches SPI personnalisees de l'ESP32-C3 (voir config.h) et
// depouille des fonctions d'affichage bitmap, inutiles pour ce projet
// (dashboard texte uniquement).
#include <Arduino.h>
#define OLED_X_MAXPIXEL 256
#define OLED_Y_MAXPIXEL 64
void er_oled_begin();
void er_oled_clear();
void er_oled_SetWindow(uint8_t Xstart, uint8_t Ystart, uint8_t Xend, uint8_t Yend);
void er_oled_char(uint8_t x, uint8_t y, const char *ascii, uint8_t mode);
void er_oled_string(uint8_t x, uint8_t y, const char *pString, uint8_t mode);

View File

@ -1,8 +1,42 @@
#include <Arduino.h> #include <Arduino.h>
#include "config.h"
#include "oled_ui.h"
#include "tms_link.h"
#include "web_ui.h"
namespace {
// Clignotement 1 Hz de la LED RGB embarquee, vert a 30% de luminosite,
// pour indiquer que le firmware tourne.
const uint32_t STATUS_LED_PERIOD_MS = 500;
const uint8_t STATUS_LED_GREEN = (uint8_t)(255 * 0.30f);
uint32_t g_lastStatusLedMs = 0;
bool g_statusLedOn = false;
void updateStatusLed() {
uint32_t now = millis();
if (now - g_lastStatusLedMs < STATUS_LED_PERIOD_MS) return;
g_lastStatusLedMs = now;
g_statusLedOn = !g_statusLedOn;
neopixelWrite(PIN_RGB_LED, 0, g_statusLedOn ? STATUS_LED_GREEN : 0, 0);
}
} // namespace
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Serial.println("ESP32-C3-dualboost");
Serial.println(TMS_DUMMY_MODE ? "Mode simulation TMS320 (dummy)" : "Mode liaison TMS320 reelle");
TmsLink::begin();
OledUi::begin();
WebUi::begin();
} }
void loop() { void loop() {
TmsLink::poll();
OledUi::update(TmsLink::telemetry(), TmsLink::linkOk());
WebUi::poll();
updateStatusLed();
} }

84
src/oled_ui.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "oled_ui.h"
#include <Arduino.h>
#include "er_oled.h"
#include "tms_link.h"
namespace OledUi {
namespace {
const uint32_t REFRESH_PERIOD_MS = 500;
const uint32_t PAGE_PERIOD_MS = 3000;
uint32_t g_lastRefreshMs = 0;
uint32_t g_lastPageChangeMs = 0;
uint8_t g_page = 0;
const uint8_t PAGE_COUNT = 3;
void drawLine(uint8_t y, const char *text) {
char padded[33];
snprintf(padded, sizeof(padded), "%-32s", text);
er_oled_string(0, y, padded, 0);
}
void drawEntreeCommandes(const Telemetry &t, bool linkOk) {
char l0[33], l1[33], l2[33], l3[33];
snprintf(l0, sizeof(l0), "TMS320: %s", linkOk ? "OK" : "PERDU");
snprintf(l1, sizeof(l1), "Vin=%.1fV Iin=%.2fA", t.vin, t.iin);
snprintf(l2, sizeof(l2), "Iout=%.2fA", t.iout);
snprintf(l3, sizeof(l3), "HT:%s PWM1:%s PWM2:%s", TmsLink::command().ht ? "ON " : "OFF",
TmsLink::command().pwm1 ? "ON " : "OFF", TmsLink::command().pwm2 ? "ON " : "OFF");
drawLine(0, l0);
drawLine(16, l1);
drawLine(32, l2);
drawLine(48, l3);
}
void drawEtage1(const Telemetry &t) {
char l1[33], l2[33];
snprintf(l1, sizeof(l1), "F=%.0fHz D=%.1f%%", t.freq1, t.duty1);
snprintf(l2, sizeof(l2), "V=%.1fV I=%.2fA T=%.1fC", t.v1, t.i1, t.t1);
drawLine(0, "ETAGE 1");
drawLine(16, l1);
drawLine(32, l2);
drawLine(48, "");
}
void drawEtage2(const Telemetry &t) {
char l1[33], l2[33];
snprintf(l1, sizeof(l1), "F=%.0fHz D=%.1f%%", t.freq2, t.duty2);
snprintf(l2, sizeof(l2), "V=%.1fV I=%.2fA T=%.1fC", t.vout, t.i2, t.t2);
drawLine(0, "ETAGE 2");
drawLine(16, l1);
drawLine(32, l2);
drawLine(48, "");
}
} // namespace
void begin() {
er_oled_begin();
er_oled_clear();
}
void update(const Telemetry &t, bool linkOk) {
uint32_t now = millis();
if (now - g_lastPageChangeMs >= PAGE_PERIOD_MS) {
g_lastPageChangeMs = now;
g_page = (g_page + 1) % PAGE_COUNT;
}
if (now - g_lastRefreshMs < REFRESH_PERIOD_MS) return;
g_lastRefreshMs = now;
switch (g_page) {
case 0: drawEntreeCommandes(t, linkOk); break;
case 1: drawEtage1(t); break;
case 2: drawEtage2(t); break;
}
}
} // namespace OledUi

13
src/oled_ui.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "protocol.h"
namespace OledUi {
void begin();
// A appeler en boucle. Redessine l'ecran periodiquement et fait tourner
// les pages (entree/commandes, etage 1, etage 2).
void update(const Telemetry &t, bool linkOk);
} // namespace OledUi

172
src/tms_link.cpp Normal file
View File

@ -0,0 +1,172 @@
#include "tms_link.h"
#include <Arduino.h>
#include <math.h>
#include "config.h"
namespace TmsLink {
namespace {
Telemetry g_telemetry;
CommandState g_command;
uint32_t g_lastValidFrameMs = 0;
uint32_t g_lastCommandSentMs = 0;
char g_lineBuf[160];
uint8_t g_lineLen = 0;
const uint32_t COMMAND_SEND_PERIOD_MS = 500;
const uint32_t SIM_UPDATE_PERIOD_MS = 200;
uint32_t g_lastSimMs = 0;
uint8_t checksumOf(const char *s, size_t len) {
uint8_t cs = 0;
for (size_t i = 0; i < len; i++) cs ^= (uint8_t)s[i];
return cs;
}
// Applique une valeur "TAG=xxx" a la telemetrie courante.
void applyField(const char *tag, float value) {
if (!strcmp(tag, "FREQ1")) g_telemetry.freq1 = value;
else if (!strcmp(tag, "FREQ2")) g_telemetry.freq2 = value;
else if (!strcmp(tag, "DUTY1")) g_telemetry.duty1 = value;
else if (!strcmp(tag, "DUTY2")) g_telemetry.duty2 = value;
else if (!strcmp(tag, "VIN")) g_telemetry.vin = value;
else if (!strcmp(tag, "IIN")) g_telemetry.iin = value;
else if (!strcmp(tag, "V1")) g_telemetry.v1 = value;
else if (!strcmp(tag, "I1")) g_telemetry.i1 = value;
else if (!strcmp(tag, "T1")) g_telemetry.t1 = value;
else if (!strcmp(tag, "VOUT")) g_telemetry.vout = value;
else if (!strcmp(tag, "I2")) g_telemetry.i2 = value;
else if (!strcmp(tag, "T2")) g_telemetry.t2 = value;
else if (!strcmp(tag, "IOUT")) g_telemetry.iout = value;
}
// Parse une ligne "$T,TAG=val,TAG=val,...*XX". Retourne true si le
// checksum est valide et que la trame a bien ete appliquee.
bool parseTelemetryLine(char *line) {
size_t len = strlen(line);
if (len < 5 || line[0] != '$' || line[1] != 'T' || line[2] != ',') return false;
char *star = strrchr(line, '*');
if (!star || star - line < 3) return false;
uint8_t rxChecksum = (uint8_t)strtol(star + 1, nullptr, 16);
uint8_t computed = checksumOf(line + 1, star - (line + 1));
if (rxChecksum != computed) return false;
*star = '\0';
char *field = line + 3; // apres "$T,"
char *saveptr = nullptr;
char *tok = strtok_r(field, ",", &saveptr);
while (tok) {
char *eq = strchr(tok, '=');
if (eq) {
*eq = '\0';
applyField(tok, atof(eq + 1));
}
tok = strtok_r(nullptr, ",", &saveptr);
}
return true;
}
void pollUart() {
while (Serial1.available()) {
char c = (char)Serial1.read();
if (c == '\n') {
g_lineBuf[g_lineLen] = '\0';
if (parseTelemetryLine(g_lineBuf)) {
g_telemetry.lastUpdateMs = millis();
g_lastValidFrameMs = g_telemetry.lastUpdateMs;
}
g_lineLen = 0;
} else if (c != '\r') {
if (g_lineLen < sizeof(g_lineBuf) - 1) {
g_lineBuf[g_lineLen++] = c;
} else {
g_lineLen = 0; // ligne trop longue, on repart de zero
}
}
}
}
// Simule un convertisseur en fonctionnement pour pouvoir developper et
// tester l'IHM (OLED + web) sans firmware TMS320.
void updateSimulation() {
uint32_t now = millis();
if (now - g_lastSimMs < SIM_UPDATE_PERIOD_MS) return;
g_lastSimMs = now;
float t = now / 1000.0f;
bool running = g_command.ht;
g_telemetry.freq1 = 100000.0f;
g_telemetry.freq2 = 100000.0f;
g_telemetry.duty1 = running && g_command.pwm1 ? 45.0f + 2.0f * sinf(t) : 0.0f;
g_telemetry.duty2 = running && g_command.pwm2 ? 50.0f + 2.0f * sinf(t + 1.0f) : 0.0f;
g_telemetry.vin = 400.0f + 1.0f * sinf(t * 0.5f);
g_telemetry.iin = running ? 1.2f + 0.1f * sinf(t) : 0.05f;
g_telemetry.v1 = running && g_command.pwm1 ? 200.0f + 0.5f * sinf(t) : 0.0f;
g_telemetry.i1 = running && g_command.pwm1 ? 2.5f + 0.2f * sinf(t * 1.3f) : 0.0f;
g_telemetry.t1 = 40.0f + 5.0f * sinf(t * 0.1f);
g_telemetry.vout = running && g_command.pwm2 ? 200.0f + 0.5f * sinf(t + 0.3f) : 0.0f;
g_telemetry.i2 = running && g_command.pwm2 ? 2.48f + 0.2f * sinf(t * 1.1f) : 0.0f;
g_telemetry.t2 = 39.5f + 5.0f * sinf(t * 0.1f + 0.5f);
g_telemetry.iout = running ? 1.05f + 0.1f * sinf(t * 0.8f) : 0.0f;
g_telemetry.lastUpdateMs = now;
g_lastValidFrameMs = now;
}
void sendCommandFrame() {
char body[48];
int n = snprintf(body, sizeof(body), "C,HT=%d,PWM1=%d,PWM2=%d", g_command.ht ? 1 : 0,
g_command.pwm1 ? 1 : 0, g_command.pwm2 ? 1 : 0);
uint8_t cs = checksumOf(body, n);
char frame[64];
snprintf(frame, sizeof(frame), "$%s*%02X\n", body, cs);
Serial1.print(frame);
}
} // namespace
void begin() {
Serial1.begin(TMS_UART_BAUD, SERIAL_8N1, PIN_TMS_UART_RX, PIN_TMS_UART_TX);
}
void poll() {
if (TMS_DUMMY_MODE) {
updateSimulation();
} else {
pollUart();
}
uint32_t now = millis();
if (now - g_lastCommandSentMs >= COMMAND_SEND_PERIOD_MS) {
g_lastCommandSentMs = now;
sendCommandFrame();
}
}
void setCommand(const CommandState &cmd) {
g_command = cmd;
sendCommandFrame();
g_lastCommandSentMs = millis();
}
const Telemetry &telemetry() { return g_telemetry; }
const CommandState &command() { return g_command; }
bool linkOk() {
if (TMS_DUMMY_MODE) return true;
return (millis() - g_lastValidFrameMs) < 2000;
}
} // namespace TmsLink

23
src/tms_link.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "protocol.h"
namespace TmsLink {
void begin();
// A appeler en boucle : lit/parse l'UART (ou simule si TMS_DUMMY_MODE) et
// envoie periodiquement la trame de commande courante.
void poll();
// Met a jour l'etat de commande a transmettre au TMS320 au prochain envoi.
void setCommand(const CommandState &cmd);
const Telemetry &telemetry();
const CommandState &command();
// true si une trame de telemetrie valide (checksum OK) a ete recue
// recemment (ou si on est en mode simulation).
bool linkOk();
} // namespace TmsLink

11
src/web_ui.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "web_ui.h"
// TODO : serveur web IHM, connexion WiFi en mode STA au reseau local
// (SSID/mot de passe a definir), une fois l'affichage OLED valide.
namespace WebUi {
void begin() {}
void poll() {}
} // namespace WebUi

12
src/web_ui.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
// Stub pour l'instant : la priorite est de valider l'affichage OLED avec
// la simulation TMS320 avant de brancher le serveur web (qui devra se
// connecter au reseau local, en mode STA, avec des identifiants a fournir).
namespace WebUi {
void begin();
void poll();
} // namespace WebUi