// ПР7. Вывод напряжения делителя на символьный дисплей LCD 16x2 по I2C
// Плата: NUCLEO-C031C6, Wokwi + STM32duino
// Автор: Алексеев Ф.В., ЭФБО-08-23
//
// Дисплей HD44780 16x2 с адаптером PCF8574 (адрес 0x27), шина I2C1:
// SDA = PB9 (D14), SCL = PB8 (D15), частота шины 100 кГц
// Биты PCF8574: P0 = RS, P1 = RW, P2 = E, P3 = подсветка, P4…P7 = D4…D7
// Делитель напряжения -> A0 (PA0), АЦП 12 бит, Uref = 3,3 В
#include <Wire.h>
#define LCD_ADDR 0x27
#define ADC_PIN A0
const uint32_t UREF_MV = 3300, ADC_MAX = 4095;
// ------------------- минимальный драйвер LCD по I2C -------------------
const uint8_t BL = 0x08, EN = 0x04, RS = 0x01;
void pcfWrite(uint8_t b) { // запись байта в расширитель PCF8574
Wire.beginTransmission(LCD_ADDR);
Wire.write(b | BL);
Wire.endTransmission();
}
void lcdNibble(uint8_t n, uint8_t mode) { // передача полубайта со стробом E
pcfWrite((n & 0xF0) | mode | EN);
delayMicroseconds(1);
pcfWrite((n & 0xF0) | mode);
delayMicroseconds(50);
}
void lcdSend(uint8_t b, uint8_t mode) { // байт = старший + младший полубайт
lcdNibble(b & 0xF0, mode);
lcdNibble((b << 4) & 0xF0, mode);
}
void lcdCmd(uint8_t c) { lcdSend(c, 0); if (c < 4) delay(2); }
void lcdChar(char ch) { lcdSend(ch, RS); }
void lcdPrint(const char *s) { while (*s) lcdChar(*s++); }
void lcdSetCursor(uint8_t col, uint8_t row) { lcdCmd(0x80 | (col + (row ? 0x40 : 0x00))); }
void lcdInit() {
delay(50);
lcdNibble(0x30, 0); delay(5); // последовательность перехода в 4-битный режим
lcdNibble(0x30, 0); delay(1);
lcdNibble(0x30, 0);
lcdNibble(0x20, 0);
lcdCmd(0x28); // 4 бита, 2 строки, шрифт 5x8
lcdCmd(0x0C); // дисплей вкл., курсор выкл.
lcdCmd(0x06); // автоинкремент адреса
lcdCmd(0x01); // очистка
}
// ------------------- формирование строк -------------------
// Строки всегда имеют длину 16 символов: экран не очищается, а перезаписывается,
// поэтому мерцания нет.
void buildLines(uint32_t code, char *l1, char *l2) {
uint32_t mv = (code * UREF_MV + ADC_MAX / 2) / ADC_MAX; // напряжение, мВ
uint32_t p10 = (code * 1000 + ADC_MAX / 2) / ADC_MAX; // процент x10
snprintf(l1, 17, "U=%lu.%03luV %3lu.%lu%%", mv / 1000, mv % 1000, p10 / 10, p10 % 10);
char bar[8];
uint8_t n = (code * 7 + ADC_MAX / 2) / ADC_MAX; // шкала из 7 ячеек
for (uint8_t i = 0; i < 7; i++) bar[i] = i < n ? (char)0xFF : '_';
bar[7] = 0;
snprintf(l2, 17, "ADC=%4lu %s", code, bar);
}
void setup() {
Serial.begin(115200);
analogReadResolution(12);
Wire.begin(); // I2C1, PB9/PB8
Wire.setClock(100000); // стандартный режим 100 кГц
lcdInit();
lcdSetCursor(0, 0); lcdPrint(" RTU MIREA PKS ");
lcdSetCursor(0, 1); lcdPrint(" Alekseev F.V. ");
delay(1500);
Serial.println("LCD1602 I2C @0x27 ready, refresh 5 Hz");
}
void loop() {
static uint32_t tLast = 0, lastCode = 0xFFFF;
if (millis() - tLast < 200) return; // обновление 5 раз в секунду
tLast = millis();
uint32_t sum = 0;
for (uint8_t i = 0; i < 8; i++) sum += analogRead(ADC_PIN);
uint32_t code = (sum + 4) / 8; // усреднение 8 отсчётов
char l1[17], l2[17];
buildLines(code, l1, l2);
lcdSetCursor(0, 0); lcdPrint(l1);
lcdSetCursor(0, 1); lcdPrint(l2);
if (code != lastCode) { // в порт — только при изменении
lastCode = code;
Serial.print(l1); Serial.print(" | ADC = "); Serial.println(code);
}
}
Loading
st-nucleo-c031c6
st-nucleo-c031c6