Mario Hüttel
43b4fd1e77
* Make Systick a 100us Timer. Millisecond ticks are still untouched. * LCD now has a 100us resolution tick * LCD uses 500us delay for waitstate * Make 'About' menu verbose: * Add 3 page menu * 1st page: Generic info * 2nd page: Version info * 3rd page: Uptime in seconds
465 lines
9.9 KiB
C
465 lines
9.9 KiB
C
/* Reflow Oven Controller
|
|
*
|
|
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
|
*
|
|
* This file is part of the Reflow Oven Controller Project.
|
|
*
|
|
* The reflow oven controller is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with the reflow oven controller project.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Thanks to
|
|
* https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/LCD-Ansteuerung
|
|
* for the basic code construct
|
|
*/
|
|
|
|
#include <stm32/stm32f4xx.h>
|
|
#include <reflow-controller/ui/lcd.h>
|
|
#include <reflow-controller/systick.h>
|
|
#include <stm-periph/clock-enable-manager.h>
|
|
#include <stm-periph/stm32-gpio-macros.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
static void lcd_port_clear(void)
|
|
{
|
|
LCD_DPORT->ODR &= ~(LCD_E_MASK);
|
|
LCD_DPORT->ODR &= ~(LCD_DATA_MASK | LCD_RS_MASK | LCD_E_MASK);
|
|
}
|
|
|
|
static void lcd_enable(void)
|
|
{
|
|
LCD_DPORT->ODR |= LCD_E_MASK;
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
//systick_wait_ms(10);
|
|
LCD_DPORT->ODR &= ~LCD_E_MASK;
|
|
//systick_wait_ms(10);
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
}
|
|
|
|
static void lcd_out(uint8_t data)
|
|
{
|
|
LCD_DPORT->ODR &= ~(LCD_DATA_MASK);
|
|
LCD_DPORT->ODR |= (data << LCD_DATA_BIT_OFFSET) & LCD_DATA_MASK;
|
|
}
|
|
|
|
static void lcd_data(uint8_t data)
|
|
{
|
|
lcd_port_clear();
|
|
LCD_DPORT->ODR |= LCD_RS_MASK;
|
|
lcd_out((data>>4) & 0xFU);
|
|
lcd_enable();
|
|
lcd_out(data & 0xFU);
|
|
lcd_enable();
|
|
systick_wait_ms(1);
|
|
}
|
|
|
|
static void lcd_command(uint8_t data)
|
|
{
|
|
lcd_port_clear();
|
|
lcd_out((data>>4) & 0xFU);
|
|
lcd_enable();
|
|
lcd_out(data & 0xFU);
|
|
lcd_enable();
|
|
systick_wait_ms(1);
|
|
}
|
|
|
|
#define LCD_DDADR_LINE1 0x00
|
|
#define LCD_DDADR_LINE2 0x40
|
|
#define LCD_DDADR_LINE3 0x10
|
|
#define LCD_DDADR_LINE4 0x50
|
|
|
|
// Clear Display -------------- 0b00000001
|
|
#define LCD_CLEAR_DISPLAY 0x01
|
|
|
|
// Cursor Home ---------------- 0b0000001x
|
|
#define LCD_CURSOR_HOME 0x02
|
|
|
|
// Set Entry Mode ------------- 0b000001xx
|
|
#define LCD_SET_ENTRY 0x04
|
|
|
|
#define LCD_ENTRY_DECREASE 0x00
|
|
#define LCD_ENTRY_INCREASE 0x02
|
|
#define LCD_ENTRY_NOSHIFT 0x00
|
|
#define LCD_ENTRY_SHIFT 0x01
|
|
|
|
// Set Display ---------------- 0b00001xxx
|
|
#define LCD_SET_DISPLAY 0x08
|
|
|
|
#define LCD_DISPLAY_OFF 0x00
|
|
#define LCD_DISPLAY_ON 0x04
|
|
#define LCD_CURSOR_OFF 0x00
|
|
#define LCD_CURSOR_ON 0x02
|
|
#define LCD_BLINKING_OFF 0x00
|
|
#define LCD_BLINKING_ON 0x01
|
|
|
|
// Set Shift ------------------ 0b0001xxxx
|
|
#define LCD_SET_SHIFT 0x10
|
|
|
|
#define LCD_CURSOR_MOVE 0x00
|
|
#define LCD_DISPLAY_SHIFT 0x08
|
|
#define LCD_SHIFT_LEFT 0x00
|
|
#define LCD_SHIFT_RIGHT 0x04
|
|
|
|
// Set Function --------------- 0b001xxxxx
|
|
#define LCD_SET_FUNCTION 0x20
|
|
|
|
#define LCD_FUNCTION_4BIT 0x00
|
|
#define LCD_FUNCTION_8BIT 0x10
|
|
#define LCD_FUNCTION_1LINE 0x00
|
|
#define LCD_FUNCTION_2LINE 0x08
|
|
#define LCD_FUNCTION_5X7 0x00
|
|
#define LCD_FUNCTION_5X10 0x04
|
|
|
|
#define LCD_SOFT_RESET 0x30
|
|
|
|
// Set CG RAM Address --------- 0b01xxxxxx (Character Generator RAM)
|
|
#define LCD_SET_CGADR 0x40
|
|
|
|
#define LCD_GC_CHAR0 0
|
|
#define LCD_GC_CHAR1 1
|
|
#define LCD_GC_CHAR2 2
|
|
#define LCD_GC_CHAR3 3
|
|
#define LCD_GC_CHAR4 4
|
|
#define LCD_GC_CHAR5 5
|
|
#define LCD_GC_CHAR6 6
|
|
#define LCD_GC_CHAR7 7
|
|
|
|
// Set DD RAM Address --------- 0b1xxxxxxx (Display Data RAM)
|
|
#define LCD_SET_DDADR 0x80
|
|
|
|
static char shadow_display[4][21];
|
|
|
|
void lcd_clear(void)
|
|
{
|
|
lcd_command(LCD_CLEAR_DISPLAY);
|
|
systick_wait_ms(3);
|
|
}
|
|
|
|
void lcd_home(void)
|
|
{
|
|
lcd_command(LCD_CURSOR_HOME);
|
|
systick_wait_ms(3);
|
|
}
|
|
|
|
static uint8_t lcd_get_set_cursor_cmd(uint8_t x, uint8_t y)
|
|
{
|
|
uint8_t data;
|
|
|
|
switch (y) {
|
|
case 0:
|
|
/* First line */
|
|
data = LCD_SET_DDADR + LCD_DDADR_LINE1 + x;
|
|
break;
|
|
|
|
case 1:
|
|
/* Second Line */
|
|
data = LCD_SET_DDADR + LCD_DDADR_LINE2 + x;
|
|
break;
|
|
|
|
case 2:
|
|
/* Third line */
|
|
data = LCD_SET_DDADR + LCD_DDADR_LINE3 + x;
|
|
break;
|
|
|
|
case 3:
|
|
/* Fourth line */
|
|
data = LCD_SET_DDADR + LCD_DDADR_LINE4 + x;
|
|
break;
|
|
|
|
default:
|
|
/* In case of wrong line, assume first line */
|
|
data = LCD_SET_DDADR + LCD_DDADR_LINE1;
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
void lcd_setcursor(uint8_t x, uint8_t y)
|
|
{
|
|
uint8_t data;
|
|
|
|
data = lcd_get_set_cursor_cmd(x, y);
|
|
lcd_command(data);
|
|
}
|
|
|
|
void lcd_string(const char *data)
|
|
{
|
|
while (*data != '\0')
|
|
lcd_data((uint8_t)*data++);
|
|
}
|
|
|
|
static void lcd_port_init()
|
|
{
|
|
LCD_DPORT->MODER &= MODER_DELETE(LCD_E) & MODER_DELETE(LCD_RS) & MODER_DELETE(LCD_DATA_BIT_OFFSET) &
|
|
MODER_DELETE(LCD_DATA_BIT_OFFSET + 1) & MODER_DELETE(LCD_DATA_BIT_OFFSET + 2) &
|
|
MODER_DELETE(LCD_DATA_BIT_OFFSET + 3);
|
|
|
|
LCD_DPORT->MODER |= OUTPUT(LCD_E) | OUTPUT(LCD_RS) | OUTPUT(LCD_DATA_BIT_OFFSET) |
|
|
OUTPUT(LCD_DATA_BIT_OFFSET + 1) | OUTPUT(LCD_DATA_BIT_OFFSET + 2) |
|
|
OUTPUT(LCD_DATA_BIT_OFFSET + 3);
|
|
}
|
|
|
|
static void lcd_clear_shadow_buff()
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = 0; j < 21; j++) {
|
|
shadow_display[i][j] = 0x0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcd_init(void)
|
|
{
|
|
int i;
|
|
|
|
rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(LCD_RCC_MASK));
|
|
lcd_port_init();
|
|
lcd_port_clear();
|
|
|
|
lcd_clear_shadow_buff();
|
|
|
|
systick_wait_ms(100);
|
|
LCD_DPORT->ODR |= (0x3 << LCD_DATA_BIT_OFFSET);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
lcd_enable();
|
|
systick_wait_ms(5);
|
|
}
|
|
|
|
// Set 4 Bit mode
|
|
lcd_port_clear();
|
|
LCD_DPORT->ODR |= (0x2<<LCD_DATA_BIT_OFFSET);
|
|
lcd_enable();
|
|
systick_wait_ms(2);
|
|
|
|
/* 4 Bit mode 2 lines */
|
|
lcd_command( LCD_SET_FUNCTION |
|
|
LCD_FUNCTION_4BIT |
|
|
LCD_FUNCTION_2LINE |
|
|
LCD_FUNCTION_5X7 );
|
|
|
|
/* Display on without cursor */
|
|
lcd_command( LCD_SET_DISPLAY |
|
|
LCD_DISPLAY_ON |
|
|
LCD_CURSOR_OFF |
|
|
LCD_BLINKING_OFF);
|
|
|
|
/* Cursor increment, no scroll */
|
|
lcd_command( LCD_SET_ENTRY |
|
|
LCD_ENTRY_INCREASE |
|
|
LCD_ENTRY_NOSHIFT );
|
|
|
|
lcd_clear();
|
|
}
|
|
|
|
void lcd_deinit()
|
|
{
|
|
lcd_port_clear();
|
|
rcc_manager_disable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(LCD_RCC_MASK));
|
|
}
|
|
|
|
static uint8_t compare_input_to_shadow(const char (*display_buffer)[21])
|
|
{
|
|
uint8_t ret = 0;
|
|
int i, row;
|
|
|
|
for (row = 0; row < 4; row++) {
|
|
for (i = 0; i < 20; i++) {
|
|
if (display_buffer[row][i] != shadow_display[row][i]) {
|
|
ret |= (1U<<row);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void lcd_fsm_enable(bool en)
|
|
{
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
if (en)
|
|
LCD_DPORT->ODR |= LCD_E_MASK;
|
|
else
|
|
LCD_DPORT->ODR &= ~LCD_E_MASK;
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
__ASM("nop");
|
|
}
|
|
|
|
static void lcd_fsm_write_command(bool high, uint8_t cmd)
|
|
{
|
|
lcd_port_clear();
|
|
|
|
if (high)
|
|
lcd_out((cmd >> 4) & 0x0F);
|
|
else
|
|
lcd_out((cmd) & 0x0F);
|
|
}
|
|
|
|
static void lcd_fsm_write_data(bool high, uint8_t data)
|
|
{
|
|
LCD_DPORT->ODR |= (1<<LCD_RS);
|
|
if (high)
|
|
lcd_out((data >> 4) & 0x0F);
|
|
else
|
|
lcd_out((data) & 0x0F);
|
|
}
|
|
|
|
enum lcd_fsm_ret lcd_fsm_write_buffer(const char (*display_buffer)[21])
|
|
{
|
|
static bool idle = true;
|
|
static uint8_t rows_to_handle = 0;
|
|
static uint32_t state_cnt;
|
|
static uint8_t row_cnt = 0;
|
|
static uint32_t char_cnt;
|
|
static uint32_t line_len;
|
|
static uint64_t timestamp = 0ULL;
|
|
enum lcd_fsm_ret ret;
|
|
|
|
ret = LCD_FSM_NOP;
|
|
|
|
if (idle) {
|
|
rows_to_handle = compare_input_to_shadow(display_buffer);
|
|
memcpy(shadow_display, display_buffer, sizeof(shadow_display));
|
|
shadow_display[0][20] = 0;
|
|
shadow_display[1][20] = 0;
|
|
shadow_display[2][20] = 0;
|
|
shadow_display[3][20] = 0;
|
|
state_cnt = 0;
|
|
row_cnt = 0;
|
|
idle = false;
|
|
}
|
|
|
|
if (rows_to_handle == 0) {
|
|
idle = true;
|
|
return ret;
|
|
}
|
|
|
|
if ((rows_to_handle & (1<<row_cnt))) {
|
|
switch (state_cnt) {
|
|
case 0:
|
|
lcd_fsm_write_command(true, lcd_get_set_cursor_cmd(0, row_cnt));
|
|
line_len = strlen(&shadow_display[row_cnt][0]);
|
|
char_cnt = 0;
|
|
lcd_fsm_enable(true);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
state_cnt++;
|
|
break;
|
|
case 1:
|
|
lcd_fsm_enable(false);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
state_cnt++;
|
|
break;
|
|
case 2:
|
|
lcd_fsm_write_command(false, lcd_get_set_cursor_cmd(0, row_cnt));
|
|
lcd_fsm_enable(true);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
state_cnt++;
|
|
break;
|
|
case 3:
|
|
lcd_fsm_enable(false);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
timestamp = systick_get_global_tick();
|
|
state_cnt++;
|
|
break;
|
|
case 4:
|
|
if (!systick_ticks_have_passed(timestamp, 4)) {
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
} else {
|
|
ret = LCD_FSM_CALL_AGAIN;
|
|
state_cnt++;
|
|
}
|
|
break;
|
|
case 5:
|
|
lcd_fsm_write_data(true, (char_cnt >= line_len) ? ' ' : shadow_display[row_cnt][char_cnt]);
|
|
lcd_fsm_enable(true);
|
|
ret = LCD_FSM_CALL_AGAIN;
|
|
state_cnt++;
|
|
break;
|
|
case 6:
|
|
lcd_fsm_enable(false);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
state_cnt++;
|
|
break;
|
|
case 7:
|
|
lcd_fsm_write_data(false, (char_cnt >= line_len) ? ' ' : shadow_display[row_cnt][char_cnt]);
|
|
lcd_fsm_enable(true);
|
|
ret = LCD_FSM_CALL_AGAIN;
|
|
state_cnt++;
|
|
break;
|
|
case 8:
|
|
lcd_fsm_enable(false);
|
|
ret = LCD_FSM_WAIT_CALL;
|
|
char_cnt++;
|
|
if (char_cnt < LCD_CHAR_WIDTH) {
|
|
state_cnt = 5;
|
|
} else {
|
|
state_cnt = 0;
|
|
rows_to_handle &= (uint8_t)~(1U<<row_cnt);
|
|
if (row_cnt < 3) {
|
|
row_cnt++;
|
|
} else {
|
|
idle = true;
|
|
row_cnt = 0;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
ret = LCD_FSM_NOP;
|
|
idle = true;
|
|
rows_to_handle = 0U;
|
|
break;
|
|
}
|
|
} else {
|
|
if (row_cnt < 3) {
|
|
row_cnt++;
|
|
ret = LCD_FSM_CALL_AGAIN;
|
|
} else {
|
|
row_cnt = 0;
|
|
ret = LCD_FSM_NOP;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|