474 lines
19 KiB
C
474 lines
19 KiB
C
/* firmware to control LCD using HD44780 driver over I²C
|
|
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include "stm8s.h"
|
|
#include "main.h"
|
|
|
|
// nMOS gate to drive backlight LED
|
|
#define BL_PORT GPIO_PA
|
|
#define BL_PIN PA3
|
|
|
|
/* usual HD44780 pinout:
|
|
* - 1 GND: ground
|
|
* - 2 VCC: 5V (3.3V versions also exist, but a less common)
|
|
* - 3 V0 : LCD bias voltage, connect to 10-20k potentiometer (VCC to GND)
|
|
* - 4 RS : Register Select (high = data, low = instruction)
|
|
* - 5 R/W: Read/Write (high = read, low = write)
|
|
* - 6 E : Enable (falling edge to latch data, high to output register)
|
|
* - 7 DB0: Data Bit 0 (for 8-bit transfer)
|
|
* - 8 DB1: Data Bit 1 (for 8-bit transfer)
|
|
* - 9 DB2: Data Bit 2 (for 8-bit transfer)
|
|
* - 10 DB3: Data Bit 3 (for 8-bit transfer)
|
|
* - 11 DB4: Data Bit 4 (for 4-bit transfer)
|
|
* - 12 DB5: Data Bit 5 (for 4-bit transfer)
|
|
* - 13 DB6: Data Bit 6 (for 4-bit transfer)
|
|
* - 14 DB7: Data Bit 7 (for 4-bit transfer)
|
|
* - 15 BLA: Backlight Anode
|
|
* - 16 BLK: Backlight Cathode
|
|
*
|
|
* we use 4-bit mode since we are fast enough to send the whole data while receiving a byte, and this saves 4 I/Os
|
|
*/
|
|
#define HD44780_RS_PORT GPIO_PC
|
|
#define HD44780_RS_PIN PC3
|
|
#define HD44780_RW_PORT GPIO_PC
|
|
#define HD44780_RW_PIN PC4
|
|
#define HD44780_E_PORT GPIO_PC
|
|
#define HD44780_E_PIN PC5
|
|
#define HD44780_DB4_PORT GPIO_PC
|
|
#define HD44780_DB4_PIN PC6
|
|
#define HD44780_DB5_PORT GPIO_PC
|
|
#define HD44780_DB5_PIN PC7
|
|
#define HD44780_DB6_PORT GPIO_PD
|
|
#define HD44780_DB6_PIN PD2
|
|
#define HD44780_DB7_PORT GPIO_PD
|
|
#define HD44780_DB7_PIN PD3
|
|
|
|
// the I²C address of this slave
|
|
static uint8_t i2c_addr = 0x47U;
|
|
#define A0_PORT GPIO_PD
|
|
#define A0_PIN PD4
|
|
#define A1_PORT GPIO_PD
|
|
#define A1_PIN PD5
|
|
#define A2_PORT GPIO_PD
|
|
#define A2_PIN PD6
|
|
|
|
// the functions we can call over I²C
|
|
enum i2c_mode_t {
|
|
// custom modes
|
|
MODE_INIT, // initialise HD44780
|
|
MODE_LINE1, // write to line 1
|
|
MODE_LINE2, // write to line 2
|
|
MODE_DISPLAY_ON, // turn display on
|
|
MODE_DISPLAY_OFF, // turn display off
|
|
MODE_BRIGHTNESS, // set backlight brightness
|
|
|
|
// raw instructions, directly mapping to HD44780
|
|
MODE_CLEAR_DISPLAY,
|
|
MODE_RETURN_HOME,
|
|
MODE_ENTRY_MODE_SET,
|
|
MODE_DISPLAY,
|
|
MODE_CURSOR_DISPLAY_SHIFT,
|
|
MODE_FUNCTION_SET,
|
|
MODE_CGRAM_ADDR,
|
|
MODE_DDRAM_ADDR,
|
|
MODE_DATA,
|
|
|
|
MODE_COUNT, // number of modes
|
|
};
|
|
|
|
static enum i2c_mode_t i2c_mode = MODE_COUNT; // set invalid value
|
|
|
|
/* actually we are fast enough to process bytes as they are received, even in 4-bit mode, except when the clear display instruction is used */
|
|
static volatile uint8_t i2c_input_buffer[2 * 20 + 1] = {0}; /**< ring buffer for received data (enough for two lines) */
|
|
static volatile uint8_t i2c_input_i = 0; /**< current position of read received data */
|
|
static volatile uint8_t i2c_input_used = 0; /**< how much data has been received and not read */
|
|
static volatile bool i2c_input_new = false; /**< if a transaction with new data started */
|
|
|
|
/**
|
|
* @warning only up to UINT32_MAX / 10 to be safe
|
|
*/
|
|
static void wait_10us(uint32_t us10)
|
|
{
|
|
us10 = ((us10 / (1 << CLK->CKDIVR.fields.HSIDIV)) * 1000) / 206; // calibrated for 1 ms
|
|
while (us10--); // burn energy
|
|
}
|
|
|
|
static void hd44780_data_direction(bool read)
|
|
{
|
|
if (read) { // switch data pins to input, with pull-up (should already be on the LCD module)
|
|
HD44780_DB7_PORT->DDR.reg &= ~HD44780_DB7_PIN; // switch data pins to input
|
|
HD44780_DB6_PORT->DDR.reg &= ~HD44780_DB6_PIN; // switch data pins to input
|
|
HD44780_DB5_PORT->DDR.reg &= ~HD44780_DB5_PIN; // switch data pins to input
|
|
HD44780_DB4_PORT->DDR.reg &= ~HD44780_DB4_PIN; // switch data pins to input
|
|
HD44780_RW_PORT->ODR.reg |= HD44780_RW_PIN; // set high to read
|
|
while (!(HD44780_RW_PORT->IDR.reg & HD44780_RW_PIN)); // wait for RW to be high
|
|
} else {
|
|
HD44780_RW_PORT->ODR.reg &= ~HD44780_RW_PIN; // set low to write
|
|
HD44780_DB7_PORT->DDR.reg |= HD44780_DB7_PIN; // switch data pins to output
|
|
HD44780_DB6_PORT->DDR.reg |= HD44780_DB6_PIN; // switch data pins to output
|
|
HD44780_DB5_PORT->DDR.reg |= HD44780_DB5_PIN; // switch data pins to output
|
|
HD44780_DB4_PORT->DDR.reg |= HD44780_DB4_PIN; // switch data pins to output
|
|
while ((HD44780_RW_PORT->IDR.reg & HD44780_RW_PIN)); // wait for RW to be low
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @note the direction and instruction/data should already be set
|
|
*/
|
|
static void hd44780_write_nibble(uint8_t nibble)
|
|
{
|
|
HD44780_E_PORT->ODR.reg |= HD44780_E_PIN; // set enable high so we can change the data
|
|
// set I/O according to nibble
|
|
if (nibble & 0x1) {
|
|
HD44780_DB4_PORT->ODR.reg |= HD44780_DB4_PIN;
|
|
} else {
|
|
HD44780_DB4_PORT->ODR.reg &= ~HD44780_DB4_PIN;
|
|
}
|
|
if (nibble & 0x2) {
|
|
HD44780_DB5_PORT->ODR.reg |= HD44780_DB5_PIN;
|
|
} else {
|
|
HD44780_DB5_PORT->ODR.reg &= ~HD44780_DB5_PIN;
|
|
}
|
|
if (nibble & 0x4) {
|
|
HD44780_DB6_PORT->ODR.reg |= HD44780_DB6_PIN;
|
|
} else {
|
|
HD44780_DB6_PORT->ODR.reg &= ~HD44780_DB6_PIN;
|
|
}
|
|
if (nibble & 0x8) {
|
|
HD44780_DB7_PORT->ODR.reg |= HD44780_DB7_PIN;
|
|
} else {
|
|
HD44780_DB7_PORT->ODR.reg &= ~HD44780_DB7_PIN;
|
|
}
|
|
// wait t_DSW = 195 ns or PW_EH = 450 ns
|
|
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // set enable low to latch data
|
|
// no need to wait t_H = 10 ns before next step since next instructions are slower
|
|
}
|
|
|
|
/**
|
|
* @note the direction and instruction/data should already be set
|
|
*/
|
|
static uint8_t hd44780_read_nibble(void)
|
|
{
|
|
HD44780_E_PORT->ODR.reg |= HD44780_E_PIN; // set enable to have data output
|
|
// wait t_DDR = 360 ns for the data to be output
|
|
uint8_t nibble = 0;
|
|
if (HD44780_DB7_PORT->IDR.reg & HD44780_DB7_PIN) {
|
|
nibble |= 0x8;
|
|
}
|
|
if (HD44780_DB6_PORT->IDR.reg & HD44780_DB6_PIN) {
|
|
nibble |= 0x4;
|
|
}
|
|
if (HD44780_DB5_PORT->IDR.reg & HD44780_DB5_PIN) {
|
|
nibble |= 0x2;
|
|
}
|
|
if (HD44780_DB4_PORT->IDR.reg & HD44780_DB4_PIN) {
|
|
nibble |= 0x1;
|
|
}
|
|
// no need to wait PW_EH = 450 ns
|
|
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // set enable low end read
|
|
|
|
return nibble;
|
|
}
|
|
|
|
static uint8_t hd44780_read_byte(void)
|
|
{
|
|
hd44780_data_direction(true); // switch to read direction
|
|
uint8_t data = (hd44780_read_nibble() << 4); // get first nibble
|
|
// no need to wait t_cycE = 500 ns before next write
|
|
data |= hd44780_read_nibble(); // get second nibble
|
|
// no need to wait tAS = 40 ns before next step since the instructions are slower
|
|
return data;
|
|
}
|
|
|
|
static uint8_t hd44780_read_bfac(void)
|
|
{
|
|
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
|
|
return hd44780_read_byte();
|
|
}
|
|
|
|
/**
|
|
* @note instruction/data should already be set
|
|
*/
|
|
static void hd44780_write_byte(uint8_t data)
|
|
{
|
|
hd44780_data_direction(false); // switch to write direction
|
|
// no need to wait tAS = 40 ns before next step since the instructions are slower
|
|
hd44780_write_nibble(data >> 4); // send first nibble
|
|
// no need to wait t_cycE = 500 ns before next write
|
|
hd44780_write_nibble(data); // send second nibble
|
|
// no need to wait t_cycE = 500 ns before next write
|
|
}
|
|
|
|
static void hd44780_write_instruction(uint8_t instruction)
|
|
{
|
|
while (hd44780_read_bfac() & 0x80); // wait until busy flag is cleared
|
|
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
|
|
hd44780_write_byte(instruction);
|
|
}
|
|
|
|
static void hd44780_write_data(uint8_t data)
|
|
{
|
|
while (hd44780_read_bfac() & 0x80); // wait until busy flag is cleared
|
|
HD44780_RS_PORT->ODR.reg |= HD44780_RS_PIN; // set high for data
|
|
hd44780_write_byte(data);
|
|
}
|
|
|
|
static void hd44780_init(void)
|
|
{
|
|
// configure display (as per datasheet)
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
|
|
hd44780_data_direction(false); // switch to write direction
|
|
wait_10us(4000 + 1000); // wait 40 ms after power up
|
|
hd44780_write_nibble(3); // 1st function write set to go to state 1 (8-bit) or 2 (4-bit first nibble) (BF cannot be checked)
|
|
wait_10us(410 + 100); // wait 4.1 ms
|
|
hd44780_write_nibble(3); // 2st function write set to go to state 1 (8-bit) or 3 (4-bit second nibble) (BF cannot be checked)
|
|
wait_10us(10 + 1); // wait 100 us
|
|
hd44780_write_nibble(3); // 3rd function write set to go to state 1 (8-bit) (BF cannot be checked)
|
|
wait_10us(4 + 1); // wait 37 us
|
|
hd44780_write_nibble(2); // switch to 4-bit mode
|
|
wait_10us(4 + 1); // wait 37 us (BF could be checked at this point)
|
|
// we are now for sure in 8-bit more (and could switch do 4-bit). 8-bit mode is actually the default after power up
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
hd44780_write_instruction(0x28); // function set (DL=1: 4-bit mode, N=1: 2 lines, F=0: 5x8 dots)
|
|
hd44780_write_instruction(0x01); // display clear
|
|
hd44780_write_instruction(0x06); // entry mode set
|
|
hd44780_write_instruction(0x02); // return home
|
|
hd44780_write_instruction(0x0c); // display on
|
|
}
|
|
|
|
void main(void)
|
|
{
|
|
sim(); // disable interrupts (while we reconfigure them)
|
|
|
|
CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide high speed internal (HSI) 16 MHz clock, we need it to process the data fast enough
|
|
// HSI clock is used for as master clock per default
|
|
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency for now (will be master clock)
|
|
while (!(CLK_ICKR & CLK_ICKR_HSIRDY)); // wait for internal oscillator to be ready
|
|
|
|
// save power by disabling unused peripheral
|
|
CLK_PCKENR1 = CLK_PCKENR1_I2C | CLK_PCKENR1_TIM25; // only keep I²C and timer 2
|
|
CLK_PCKENR2 = CLK_PCKENR2_AWU; // only keep AWU
|
|
|
|
// configure independent watchdog (very loose, just it case the firmware hangs)
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
IWDG_KR = IWDG_KR_KEY_ENABLE; // start watchdog
|
|
IWDG_KR = IWDG_KR_KEY_ACCESS; // allows changing the prescale
|
|
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
/* configure HD44780 pins
|
|
* the display is a lot more stable the operated in push-pull mode
|
|
* but they can also be operated in open-drain mode (as does the PCF8574),
|
|
* since all pins are pulled up by the HD44780 , except E which requires an external pull-up resistor (~1 kO).
|
|
* when operated in open-drain mode, wait the recommended maximum operation times since reading is error prone
|
|
*/
|
|
HD44780_RS_PORT->DDR.reg |= HD44780_RS_PIN; // switch pin to output
|
|
HD44780_RS_PORT->CR1.reg &= ~HD44780_RS_PIN; // use in open-drain mode
|
|
HD44780_RS_PORT->CR1.reg |= HD44780_RS_PIN; // use in push-pull mode
|
|
HD44780_RW_PORT->DDR.reg |= HD44780_RW_PIN; // switch pin to output
|
|
HD44780_RW_PORT->CR1.reg &= ~HD44780_RW_PIN; // use in open-drain mode
|
|
HD44780_RW_PORT->CR1.reg |= HD44780_RW_PIN; // use in push-pull mode
|
|
HD44780_E_PORT->DDR.reg |= HD44780_E_PIN; // switch pin to output
|
|
HD44780_E_PORT->CR1.reg |= HD44780_E_PIN; // use in push-pull mode
|
|
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // start idle low
|
|
HD44780_DB7_PORT->DDR.reg |= HD44780_DB7_PIN; // switch pin to output
|
|
HD44780_DB7_PORT->CR1.reg &= ~HD44780_DB7_PIN; // use in open-drain mode
|
|
HD44780_DB7_PORT->CR1.reg |= HD44780_DB7_PIN; // use in push-pull mode
|
|
HD44780_DB6_PORT->DDR.reg |= HD44780_DB6_PIN; // switch pin to output
|
|
HD44780_DB6_PORT->CR1.reg &= ~HD44780_DB6_PIN; // use in open-drain mode
|
|
HD44780_DB6_PORT->CR1.reg |= HD44780_DB6_PIN; // use in push-pull mode
|
|
HD44780_DB5_PORT->DDR.reg |= HD44780_DB5_PIN; // switch pin to output
|
|
HD44780_DB5_PORT->CR1.reg &= ~HD44780_DB5_PIN; // use in open-drain mode
|
|
HD44780_DB5_PORT->CR1.reg |= HD44780_DB5_PIN; // use in push-pull mode
|
|
HD44780_DB4_PORT->DDR.reg |= HD44780_DB4_PIN; // switch pin to output
|
|
HD44780_DB4_PORT->CR1.reg &= ~HD44780_DB4_PIN; // use in open-drain mode
|
|
HD44780_DB4_PORT->CR1.reg |= HD44780_DB4_PIN; // use in push-pull mode
|
|
hd44780_data_direction(false); // configure pins as output
|
|
|
|
// read I²C address bits
|
|
A0_PORT->DDR.reg &= ~A0_PIN; // switch pin to input
|
|
A0_PORT->CR1.reg |= A0_PIN; // enable pull-up
|
|
if (A0_PORT->IDR.reg & A0_PIN) {
|
|
i2c_addr |= 0x01;
|
|
} else {
|
|
i2c_addr &= ~0x01;
|
|
}
|
|
A1_PORT->DDR.reg &= ~A1_PIN; // switch pin to input
|
|
A1_PORT->CR1.reg |= A1_PIN; // enable pull-up
|
|
if (A1_PORT->IDR.reg & A1_PIN) {
|
|
i2c_addr |= 0x02;
|
|
} else {
|
|
i2c_addr &= ~0x02;
|
|
}
|
|
A2_PORT->DDR.reg &= ~A2_PIN; // switch pin to input
|
|
A2_PORT->CR1.reg |= A2_PIN; // enable pull-up
|
|
if (A2_PORT->IDR.reg & A2_PIN) {
|
|
i2c_addr |= 0x04;
|
|
} else {
|
|
i2c_addr &= ~0x04;
|
|
}
|
|
|
|
// configure I²C
|
|
GPIO_PB->CR1.reg |= (PB4 | PB5); // enable internal pull-up on SCL/SDA
|
|
GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral
|
|
I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral (must be done before any other register is written)
|
|
I2C_CR2 |= I2C_CR2_STOP; // release lines
|
|
I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit
|
|
while (0 == (GPIO_PB->IDR.reg & PB4)); // wait for SCL line to be released
|
|
while (0 == (GPIO_PB->IDR.reg & PB5)); // wait for SDA line to be released
|
|
I2C_CR2 &= ~I2C_CR2_SWRST; // release reset
|
|
I2C_CR1 |= I2C_CR1_ENGC; // enable general call
|
|
I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral
|
|
I2C_FREQR = 16; // the peripheral frequency is 4 MHz (must match CPU frequency)
|
|
//I2C_CR1 |= I2C_CR1_ENGC; // enable general call (I was not able to have slave select with address 0x00 ACKed)
|
|
I2C_CR2 |= I2C_CR2_ACK; // enable acknowledgement if address matches
|
|
// since we are slave and not master, we don't have to set CCR
|
|
I2C_OARL = (i2c_addr << 1U); // set slave address
|
|
I2C_ITR |= (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN); // enable buffer and event interrupts
|
|
|
|
// configure auto-wakeup (AWU) to be able to refresh the watchdog
|
|
// 128 kHz LSI used by default in option bytes CKAWUSEL
|
|
// we skip measuring the LS clock frequency since there is no need to be precise
|
|
AWU->TBR.fields.AWUTB = 10; // interval range: 128-256 ms
|
|
AWU->APR.fields.APR = 0x3e; // set time to 256 ms
|
|
AWU_CSR |= AWU_CSR_AWUEN; // enable AWU (start only when entering wait or active halt mode)
|
|
|
|
// configure PWM output for backlight
|
|
BL_PORT->DDR.reg |= BL_PIN; // switch pin to output
|
|
BL_PORT->CR1.reg |= BL_PIN; // use in push-pull mode
|
|
TIM2->CCMR3.output_fields.OC3M = 6; // PWM mode 1
|
|
TIM2->CCMR3.output_fields.CC3S = 0; // configure channel as output
|
|
TIM2->CCER2.fields.CC3P = 0; // active high
|
|
TIM2->CCER2.fields.CC3E = 1; // output enable
|
|
TIM2->ARRH.fields.ARR15_8 = 0; // set reload to 0xff
|
|
TIM2->ARRL.fields.ARR7_0 = 0xfe; // set reload to 0xff
|
|
TIM2->PSCR.fields.PSC = 6; // set frequency to 1 kHz
|
|
TIM2->CCR3H.fields.CCR3 = 0; // start with PWM off
|
|
TIM2->CCR3L.fields.CCR3 = 0; // start with PWM off
|
|
TIM2->EGR.fields.UG = 1; // force reload of values
|
|
TIM2->CR1.fields.CEN = 1; // enable timer
|
|
|
|
hd44780_init(); // initialise display
|
|
|
|
rim(); // re-enable interrupts
|
|
|
|
while (true) {
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
while (i2c_input_used) { // I²C data is available
|
|
sim(); // disable interrupt while reading buffer to not corrupt indexes
|
|
uint8_t input_data = i2c_input_buffer[i2c_input_i]; // get start buffered data
|
|
i2c_input_i = (i2c_input_i + 1) % ARRAY_LENGTH(i2c_input_buffer); // update used buffer
|
|
i2c_input_used--; // update used buffer
|
|
rim(); // re-enable interrupts
|
|
if (i2c_input_new) { // this is the start of a transaction, the first byte indicates the mode to be used
|
|
i2c_mode = input_data; // set user provided mode (no need to check since the undefined modes don't do anything)
|
|
i2c_input_new = false; // clear flag
|
|
// process mode switch
|
|
switch (i2c_mode) {
|
|
case MODE_INIT: // re-initialise display
|
|
hd44780_init();
|
|
break;
|
|
case MODE_CLEAR_DISPLAY: // clear display
|
|
hd44780_write_instruction(0x01); // clear display instruction
|
|
break;
|
|
case MODE_LINE1:
|
|
hd44780_write_instruction(0x80); // set DDRAM address to 0 (line 1)
|
|
break;
|
|
case MODE_LINE2:
|
|
hd44780_write_instruction(0xc0); // set DDRAM address to 0x40 (line 2)
|
|
break;
|
|
case MODE_DISPLAY_ON:
|
|
hd44780_write_instruction(0x0c); // display on
|
|
break;
|
|
case MODE_DISPLAY_OFF:
|
|
hd44780_write_instruction(0x08); // display off
|
|
break;
|
|
case MODE_RETURN_HOME:
|
|
hd44780_write_instruction(0x02); // return home
|
|
break;
|
|
default:
|
|
break; // waiting for data
|
|
}
|
|
} else {
|
|
// process data
|
|
// note set RS at every byte since read busy always switches it to instruction
|
|
switch (i2c_mode) {
|
|
case MODE_LINE1:
|
|
case MODE_LINE2:
|
|
case MODE_DATA:
|
|
hd44780_write_data(input_data);
|
|
break;
|
|
case MODE_ENTRY_MODE_SET:
|
|
hd44780_write_instruction(0x04 | (input_data & 0x3));
|
|
break;
|
|
case MODE_DISPLAY:
|
|
hd44780_write_instruction(0x08 | (input_data & 0x7));
|
|
break;
|
|
case MODE_CURSOR_DISPLAY_SHIFT:
|
|
hd44780_write_instruction(0x10 | (input_data & 0xc));
|
|
break;
|
|
case MODE_FUNCTION_SET:
|
|
hd44780_write_instruction(0x20 | (input_data & 0xc)); // keep DL=0 4-bit mode
|
|
break;
|
|
case MODE_CGRAM_ADDR:
|
|
hd44780_write_instruction(0x40 | (input_data & 0x3f));
|
|
break;
|
|
case MODE_DDRAM_ADDR:
|
|
hd44780_write_instruction(0x80 | (input_data & 0x3f));
|
|
break;
|
|
case MODE_BRIGHTNESS:
|
|
TIM2->CCR3L.fields.CCR3 = input_data; // set duty cycle
|
|
break;
|
|
default: // read values do not make sense
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//for (volatile uint32_t wait = 0; wait < 100000; wait++);
|
|
wfi(); // go to wait mode (halt would prevent slave select to be acknowledged)
|
|
}
|
|
}
|
|
|
|
void awu(void) __interrupt(IRQ_AWU) // auto wakeup
|
|
{
|
|
volatile uint8_t awuf = AWU_CSR; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization)
|
|
// let the main loop kick the dog
|
|
}
|
|
|
|
void i2c(void) __interrupt(IRQ_I2C) // auto wakeup
|
|
{
|
|
// make copies of status registers, since some bits might be cleared meanwhile
|
|
uint8_t sr1 = I2C_SR1;
|
|
uint8_t sr2 = I2C_SR2;
|
|
uint8_t sr3 = I2C_SR3;
|
|
if (sr1 & I2C_SR1_TXE) { // transmission buffer is empty
|
|
I2C_DR = 0xff; // read is not a valid command, return anything
|
|
}
|
|
if (sr1 & I2C_SR1_RXNE) { // receive buffer is full
|
|
uint8_t data = I2C_DR; // read data (also clears flag);
|
|
if (i2c_input_used < ARRAY_LENGTH(i2c_input_buffer)) { // only store when there is place, else drop new data
|
|
i2c_input_buffer[(i2c_input_i + i2c_input_used) % ARRAY_LENGTH(i2c_input_buffer)] = data; // save new data
|
|
i2c_input_used++; // update used buffer
|
|
}
|
|
}
|
|
if (sr1 & I2C_SR1_STOPF) { // stop received
|
|
I2C_CR2 |= I2C_CR2_ACK; // this is just to clear the flag
|
|
}
|
|
if (sr1 & I2C_SR1_ADDR) { // our slave address has been selected
|
|
if (sr3 & I2C_SR3_TRA) { // we will have to transmit data
|
|
} else { // we will receive data
|
|
i2c_input_new = true; // informs a new transaction started
|
|
}
|
|
}
|
|
if (sr1 & I2C_SR1_BTF) { // byte transfer finished (only set when stretching has been enabled)
|
|
// cleared by reading/writing from/to DR or when stop is received
|
|
}
|
|
if (sr2 & I2C_SR2_AF) { // NACK received (e.g. end of read transaction)
|
|
I2C_SR2 &= ~I2C_SR2_AF; // clear flag
|
|
}
|
|
}
|