driver/fw: I2C LCD working
This commit is contained in:
parent
010d5587b7
commit
668c89f889
11 changed files with 1060 additions and 2 deletions
|
|
@ -50,7 +50,7 @@ cmsis_exports.c: $(CMSIS_DEV_PATH)/Include/stm32f030x6.h $(CMSIS_PATH)/Include/c
|
|||
%.dot: %.elf
|
||||
r2 -a arm -qc 'aa;agC' $< 2>/dev/null >$@
|
||||
|
||||
main.elf: main.o startup_stm32f030x6.o system_stm32f0xx.o $(HAL_PATH)/Src/stm32f0xx_ll_utils.o cmsis_exports.o ../common/8b10b.o serial.o mac.o
|
||||
main.elf: main.o startup_stm32f030x6.o system_stm32f0xx.o $(HAL_PATH)/Src/stm32f0xx_ll_utils.o cmsis_exports.o ../common/8b10b.o serial.o mac.o i2c.o lcd1602.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||
$(OBJCOPY) -O ihex $@ $(@:.elf=.hex)
|
||||
$(OBJCOPY) -O binary $@ $(@:.elf=.bin)
|
||||
|
|
|
|||
58
driver_fw/global.h
Normal file
58
driver_fw/global.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GLOBAL_H__
|
||||
#define __GLOBAL_H__
|
||||
|
||||
/* Workaround for sub-par ST libraries */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
|
||||
#include <stm32f0xx.h>
|
||||
#include <stm32f0xx_ll_utils.h>
|
||||
#include <stm32f0xx_ll_spi.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <system_stm32f0xx.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Microcontroller part number: STM32F030F4P6 */
|
||||
|
||||
/* Things used for module status reporting. */
|
||||
#define FIRMWARE_VERSION 1
|
||||
#define HARDWARE_VERSION 3
|
||||
|
||||
#define TS_CAL1 (*(uint16_t *)0x1FFFF7B8)
|
||||
#define VREFINT_CAL (*(uint16_t *)0x1FFFF7BA)
|
||||
|
||||
#define STATUS_LED_DURATION_MS 200
|
||||
#define TICK_MS 10
|
||||
|
||||
extern volatile unsigned int sys_time_tick;
|
||||
extern volatile unsigned int sys_time_ms;
|
||||
extern volatile unsigned int sys_time_s;
|
||||
extern unsigned int frame_duration_us;
|
||||
|
||||
extern volatile uint8_t global_brightness;
|
||||
|
||||
void trigger_error_led(void);
|
||||
void trigger_comm_led(void);
|
||||
|
||||
#endif/*__GLOBAL_H__*/
|
||||
236
driver_fw/i2c.c
Normal file
236
driver_fw/i2c.c
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// Inter-integrated circuit (I2C) management
|
||||
|
||||
|
||||
#include "i2c.h"
|
||||
|
||||
|
||||
// I2C timeout, about 2ms
|
||||
#define I2C_TIMEOUT 200U
|
||||
|
||||
// Maximum NBYTES value
|
||||
#define I2C_NBYTES_MAX 255U
|
||||
|
||||
|
||||
// Count rough delay for timeouts
|
||||
static uint32_t i2c_calc_delay(uint32_t delay) {
|
||||
uint32_t cnt;
|
||||
|
||||
if (SystemCoreClock > 1000000U) {
|
||||
cnt = (delay * ((SystemCoreClock / 1000000U) + 1U));
|
||||
} else {
|
||||
cnt = (((delay / 100U) + 1U) * ((SystemCoreClock / 10000U) + 1U));
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// Check if target device is ready for communication
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// devAddr - target device address
|
||||
// trials - number of trials (must not be zero)
|
||||
// return:
|
||||
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
||||
I2CSTATUS i2c_is_device_ready(I2C_TypeDef* I2Cx, uint8_t devAddr, uint32_t trials) {
|
||||
volatile uint32_t wait;
|
||||
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
||||
uint32_t reg;
|
||||
|
||||
while (trials--) {
|
||||
// Clear all flags
|
||||
I2Cx->ICR = I2C_ICR_ALL;
|
||||
|
||||
// Generate START
|
||||
i2c_genstart(I2Cx, devAddr);
|
||||
|
||||
// Wait for STOP, NACK or BERR
|
||||
wait = delay_val;
|
||||
while (!((reg = I2Cx->ISR) & (I2C_ISR_STOPF | I2C_ISR_NACKF | I2C_ISR_BERR)) && --wait);
|
||||
if (wait == 0) { return I2C_ERROR; }
|
||||
|
||||
// Wait while STOP flag is reset
|
||||
wait = delay_val;
|
||||
while (!(I2Cx->ISR & I2C_ISR_STOPF) && --wait);
|
||||
if (wait == 0) { return I2C_ERROR; }
|
||||
|
||||
// Clear the NACK, STOP and BERR flags
|
||||
I2Cx->ICR = I2C_ICR_STOPCF | I2C_ICR_NACKCF | I2C_ICR_BERRCF;
|
||||
|
||||
// Check for BERR flag
|
||||
if (reg & I2C_ISR_BERR) {
|
||||
// Misplaced START/STOP? Perform a software reset of I2C
|
||||
i2c_disable(I2Cx);
|
||||
i2c_enable(I2Cx);
|
||||
} else {
|
||||
// Device responded if NACK flag is not set
|
||||
if (!(reg & I2C_ISR_NACKF)) { return I2C_SUCCESS; }
|
||||
}
|
||||
}
|
||||
|
||||
return I2C_ERROR;
|
||||
}
|
||||
|
||||
// Transmit an amount of data in master mode
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// pBbuf - pointer to the data buffer
|
||||
// nbytes - number of bytes to transmit
|
||||
// devAddr - address of target device
|
||||
// flags - options for transmission, combination of I2C_TX_xx values:
|
||||
// I2C_TX_NOSTART - don't generate START condition
|
||||
// I2C_TX_NOSTOP - don't generate STOP condition
|
||||
// I2C_TX_CONT - this flag indicates that transmission will be continued
|
||||
// e.g. by calling this function again with NOSTART flag
|
||||
// zero value - generate both START and STOP conditions
|
||||
// return:
|
||||
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
||||
I2CSTATUS i2c_transmit(I2C_TypeDef* I2Cx, const uint8_t *pBuf, uint32_t nbytes, uint8_t devAddr, uint32_t flags) {
|
||||
uint32_t reg;
|
||||
uint32_t tx_count;
|
||||
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
||||
volatile uint32_t wait;
|
||||
|
||||
// Clear all flags
|
||||
I2Cx->ICR = I2C_ICR_ALL;
|
||||
|
||||
// Everything regarding to the transmission is in the CR2 register
|
||||
reg = I2Cx->CR2;
|
||||
reg &= ~I2C_CR2_ALL;
|
||||
|
||||
// Slave device address
|
||||
reg |= (devAddr & I2C_CR2_SADD);
|
||||
|
||||
// Whether it need to generate START condition
|
||||
if (!(flags & I2C_TX_NOSTART)) { reg |= I2C_CR2_START; }
|
||||
|
||||
// Whether it need to generate STOP condition
|
||||
if ((flags & I2C_TX_CONT) || (nbytes > I2C_NBYTES_MAX)) {
|
||||
reg |= I2C_CR2_RELOAD;
|
||||
} else {
|
||||
if (!(flags & I2C_TX_NOSTOP)) { reg |= I2C_CR2_AUTOEND; }
|
||||
}
|
||||
|
||||
// Transfer length
|
||||
tx_count = (nbytes > I2C_NBYTES_MAX) ? I2C_NBYTES_MAX : nbytes;
|
||||
nbytes -= tx_count;
|
||||
reg |= tx_count << I2C_CR2_NBYTES_Pos;
|
||||
|
||||
// Write a composed value to the I2C register
|
||||
I2Cx->CR2 = reg;
|
||||
|
||||
// Transmit data
|
||||
while (tx_count) {
|
||||
// Wait until either TXIS or NACK flag is set
|
||||
wait = delay_val;
|
||||
while (!((reg = I2Cx->ISR) & (I2C_ISR_TXIS | I2C_ISR_NACKF)) && --wait);
|
||||
if ((reg & I2C_ISR_NACKF) || (wait == 0)) { return I2C_ERROR; }
|
||||
|
||||
// Transmit byte
|
||||
I2Cx->TXDR = *pBuf++;
|
||||
tx_count--;
|
||||
|
||||
if ((tx_count == 0) && (nbytes != 0)) {
|
||||
// Wait until TCR flag is set (Transfer Complete Reload)
|
||||
wait = delay_val;
|
||||
while (!(I2Cx->ISR & I2C_ISR_TCR) && --wait);
|
||||
if (wait == 0) { return I2C_ERROR; }
|
||||
|
||||
// Configure next (or last) portion transfer
|
||||
reg = I2Cx->CR2;
|
||||
reg &= ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD | I2C_CR2_AUTOEND);
|
||||
if ((flags & I2C_TX_CONT) || (nbytes > I2C_NBYTES_MAX)) {
|
||||
reg |= I2C_CR2_RELOAD;
|
||||
} else {
|
||||
if (!(flags & I2C_TX_NOSTOP)) { reg |= I2C_CR2_AUTOEND; }
|
||||
}
|
||||
tx_count = (nbytes > I2C_NBYTES_MAX) ? I2C_NBYTES_MAX : nbytes;
|
||||
nbytes -= tx_count;
|
||||
reg |= tx_count << I2C_CR2_NBYTES_Pos;
|
||||
I2Cx->CR2 = reg;
|
||||
}
|
||||
}
|
||||
|
||||
// End of transmission
|
||||
wait = delay_val;
|
||||
while (!(I2Cx->ISR & (I2C_ISR_TC | I2C_ISR_TCR | I2C_ISR_STOPF)) && --wait);
|
||||
|
||||
return (wait) ? I2C_SUCCESS : I2C_ERROR;
|
||||
}
|
||||
|
||||
// Receive an amount of data in master mode
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// buf - pointer to the data buffer
|
||||
// nbytes - number of bytes to receive
|
||||
// devAddr - address of target device
|
||||
// return:
|
||||
// I2C_ERROR if there was a timeout during I2C operations, I2C_SUCCESS otherwise
|
||||
I2CSTATUS i2c_receive(I2C_TypeDef* I2Cx, uint8_t *pBuf, uint32_t nbytes, uint8_t devAddr) {
|
||||
uint32_t reg;
|
||||
uint32_t rx_count;
|
||||
uint32_t delay_val = i2c_calc_delay(I2C_TIMEOUT);
|
||||
volatile uint32_t wait;
|
||||
|
||||
// Clear all flags
|
||||
I2Cx->ICR = I2C_ICR_ALL;
|
||||
|
||||
// Everything regarding to the transmission is in the CR2 register
|
||||
reg = I2Cx->CR2;
|
||||
reg &= ~I2C_CR2_ALL;
|
||||
|
||||
// Configure slave device address, enable START condition and set direction to READ
|
||||
reg |= (devAddr & I2C_CR2_SADD) | I2C_CR2_START | I2C_CR2_RD_WRN;
|
||||
|
||||
// Transfer length
|
||||
if (nbytes > I2C_NBYTES_MAX) {
|
||||
rx_count = I2C_NBYTES_MAX;
|
||||
reg |= I2C_CR2_RELOAD;
|
||||
} else {
|
||||
rx_count = nbytes;
|
||||
reg |= I2C_CR2_AUTOEND;
|
||||
}
|
||||
reg |= rx_count << I2C_CR2_NBYTES_Pos;
|
||||
nbytes -= rx_count;
|
||||
|
||||
// Write a composed value to the I2C register
|
||||
I2Cx->CR2 = reg;
|
||||
|
||||
// Receive data
|
||||
while (rx_count) {
|
||||
// Wait until either RXNE or NACK flag is set
|
||||
wait = delay_val;
|
||||
while (!((reg = I2Cx->ISR) & (I2C_ISR_RXNE | I2C_ISR_NACKF)) && --wait);
|
||||
if ((reg & I2C_ISR_NACKF) || (wait == 0)) { return I2C_ERROR; }
|
||||
|
||||
// Read received data
|
||||
*pBuf++ = I2Cx->RXDR;
|
||||
rx_count--;
|
||||
|
||||
if ((rx_count == 0) && (nbytes != 0)) {
|
||||
// Wait until TCR flag is set (Transfer Complete Reload)
|
||||
wait = delay_val;
|
||||
while (!(I2Cx->ISR & I2C_ISR_TCR) && --wait);
|
||||
if (wait == 0) { return I2C_ERROR; }
|
||||
|
||||
// Configure next (or last) portion transfer
|
||||
reg = I2Cx->CR2;
|
||||
reg &= ~(I2C_CR2_NBYTES | I2C_CR2_AUTOEND | I2C_CR2_RELOAD);
|
||||
if (nbytes > I2C_NBYTES_MAX) {
|
||||
rx_count = I2C_NBYTES_MAX;
|
||||
reg |= I2C_CR2_RELOAD;
|
||||
} else {
|
||||
rx_count = nbytes;
|
||||
reg |= I2C_CR2_AUTOEND;
|
||||
}
|
||||
reg |= rx_count << I2C_CR2_NBYTES_Pos;
|
||||
nbytes -= rx_count;
|
||||
I2Cx->CR2 = reg;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the STOP flag
|
||||
wait = delay_val;
|
||||
while (!(I2Cx->ISR & I2C_ISR_STOPF) && --wait);
|
||||
|
||||
return (wait) ? I2C_SUCCESS : I2C_ERROR;
|
||||
}
|
||||
146
driver_fw/i2c.h
Normal file
146
driver_fw/i2c.h
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#ifndef __I2C_H
|
||||
#define __I2C_H
|
||||
|
||||
#include "global.h"
|
||||
|
||||
// I2C HAL
|
||||
|
||||
// I2C1
|
||||
// SCL [PB6, PB8]
|
||||
#define I2C1_SCL_GPIO_PERIPH RCC_AHB2ENR_GPIOBEN
|
||||
#define I2C1_SCL_GPIO_PORT GPIOB
|
||||
#define I2C1_SCL_GPIO_PIN GPIO_PIN_8
|
||||
#define I2C1_SCL_GPIO_SRC GPIO_PinSource8
|
||||
// SDA [PB7, PB9]
|
||||
#define I2C1_SDA_GPIO_PERIPH RCC_AHB2ENR_GPIOBEN
|
||||
#define I2C1_SDA_GPIO_PORT GPIOB
|
||||
#define I2C1_SDA_GPIO_PIN GPIO_PIN_9
|
||||
#define I2C1_SDA_GPIO_SRC GPIO_PinSource9
|
||||
|
||||
// I2C2
|
||||
// SCL [PB10, PB13]
|
||||
#define I2C2_SCL_GPIO_PERIPH RCC_AHB2ENR_GPIOBEN
|
||||
#define I2C2_SCL_GPIO_PORT GPIOB
|
||||
#define I2C2_SCL_GPIO_PIN GPIO_PIN_10
|
||||
#define I2C2_SCL_GPIO_SRC GPIO_PinSource10
|
||||
// SDA [PB11, PB14]
|
||||
#define I2C2_SDA_GPIO_PERIPH RCC_AHB2ENR_GPIOBEN
|
||||
#define I2C2_SDA_GPIO_PORT GPIOB
|
||||
#define I2C2_SDA_GPIO_PIN GPIO_PIN_11
|
||||
#define I2C2_SDA_GPIO_SRC GPIO_PinSource11
|
||||
|
||||
// I2C3
|
||||
// SCL [PC0]
|
||||
#define I2C3_SCL_GPIO_PERIPH RCC_AHB2ENR_GPIOCEN
|
||||
#define I2C3_SCL_GPIO_PORT GPIOC
|
||||
#define I2C3_SCL_GPIO_PIN GPIO_PIN_0
|
||||
#define I2C3_SCL_GPIO_SRC GPIO_PinSource0
|
||||
// SDA [PC1]
|
||||
#define I2C3_SDA_GPIO_PERIPH RCC_AHB2ENR_GPIOCEN
|
||||
#define I2C3_SDA_GPIO_PORT GPIOC
|
||||
#define I2C3_SDA_GPIO_PIN GPIO_PIN_1
|
||||
#define I2C3_SDA_GPIO_SRC GPIO_PinSource1
|
||||
|
||||
|
||||
// Definitions of I2C analog filter state
|
||||
#define I2C_AF_ENABLE ((uint32_t)0x00000000U) // Analog filter is enabled
|
||||
#define I2C_AF_DISABLE I2C_CR1_ANFOFF // Analog filter is disabled
|
||||
|
||||
// Flags definitions for transmit function
|
||||
#define I2C_TX_STOP ((uint32_t)0x00000000U) // Generate STOP condition
|
||||
#define I2C_TX_NOSTOP ((uint32_t)0x10000000U) // Don't generate STOP condition
|
||||
#define I2C_TX_NOSTART ((uint32_t)0x20000000U) // Don't generate START condition
|
||||
#define I2C_TX_CONT ((uint32_t)0x40000000U) // The transmission will be continued
|
||||
// Definitions for compatibility with old code using this library
|
||||
#define I2C_GENSTOP_YES I2C_TX_STOP
|
||||
#define I2C_GENSTOP_NO I2C_TX_NOSTOP
|
||||
|
||||
// Definition of bits to reset in CR2 register
|
||||
#define I2C_CR2_ALL (I2C_CR2_SADD | \
|
||||
I2C_CR2_NBYTES | \
|
||||
I2C_CR2_RELOAD | \
|
||||
I2C_CR2_AUTOEND | \
|
||||
I2C_CR2_RD_WRN | \
|
||||
I2C_CR2_START | \
|
||||
I2C_CR2_STOP)
|
||||
|
||||
// Definition of all bits in ICR register (clear all I2C flags at once)
|
||||
#define I2C_ICR_ALL (I2C_ICR_ADDRCF | \
|
||||
I2C_ICR_ALERTCF | \
|
||||
I2C_ICR_ARLOCF | \
|
||||
I2C_ICR_BERRCF | \
|
||||
I2C_ICR_NACKCF | \
|
||||
I2C_ICR_OVRCF | \
|
||||
I2C_ICR_PECCF | \
|
||||
I2C_ICR_STOPCF | \
|
||||
I2C_ICR_TIMOUTCF)
|
||||
|
||||
|
||||
// Result of I2C functions
|
||||
typedef enum {
|
||||
I2C_ERROR = 0,
|
||||
I2C_SUCCESS = !I2C_ERROR
|
||||
} I2CSTATUS;
|
||||
|
||||
|
||||
// Public functions and macros
|
||||
|
||||
// Enable I2C peripheral
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
static inline void i2c_enable(I2C_TypeDef* I2Cx) {
|
||||
I2Cx->CR1 |= I2C_CR1_PE;
|
||||
}
|
||||
|
||||
// Disable I2C peripheral
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
static inline void i2c_disable(I2C_TypeDef* I2Cx) {
|
||||
I2Cx->CR1 &= ~I2C_CR1_PE;
|
||||
}
|
||||
|
||||
// Configure I2C noise filters
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// af - analog filter state, I2C_AF_DISABLE or I2C_AF_ENABLE
|
||||
// df - digital filter configuration, can be a value in range from 0 to 15
|
||||
// zero value means the digital filter is disabled
|
||||
// this values means filtering capability up to (df * ti2cclk)
|
||||
// note: must be called only when I2C is disabled (PE bit in I2C_CR1 register is reset)
|
||||
static inline void i2c_config_filters(I2C_TypeDef* I2Cx, uint32_t af, uint32_t df) {
|
||||
I2Cx->CR1 &= ~(I2C_CR1_ANFOFF | I2C_CR1_DNF);
|
||||
I2Cx->CR1 |= (af & I2C_CR1_ANFOFF) | ((df << I2C_CR1_DNF_Pos) & I2C_CR1_DNF);
|
||||
}
|
||||
|
||||
// Configure the I2C timings (SDA setup/hold time and SCL high/low period)
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// timing - the value for I2C_TIMINGR register
|
||||
// note: must be called only when I2C is disabled (PE bit in I2C_CR1 register is reset)
|
||||
static inline void i2c_config_timing(I2C_TypeDef* I2Cx, uint32_t timing) {
|
||||
I2Cx->TIMINGR = timing;
|
||||
}
|
||||
|
||||
// Generate START condition
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
// addr - I2C device address
|
||||
// note: 7-bit addressing mode
|
||||
static inline void i2c_genstart(I2C_TypeDef* I2Cx, uint32_t addr) {
|
||||
I2Cx->CR2 = (addr & I2C_CR2_SADD) | I2C_CR2_START | I2C_CR2_AUTOEND;
|
||||
}
|
||||
|
||||
// Generate STOP condition
|
||||
// input:
|
||||
// I2Cx - pointer to the I2C peripheral (I2C1, etc.)
|
||||
static inline void i2c_genstop(I2C_TypeDef* I2Cx) {
|
||||
I2Cx->CR2 |= I2C_CR2_STOP;
|
||||
}
|
||||
|
||||
|
||||
// Function prototypes
|
||||
I2CSTATUS i2c_is_device_ready(I2C_TypeDef* I2Cx, uint8_t devAddr, uint32_t Trials);
|
||||
I2CSTATUS i2c_transmit(I2C_TypeDef* I2Cx, const uint8_t *pBuf, uint32_t nbytes, uint8_t devAddr, uint32_t flags);
|
||||
I2CSTATUS i2c_receive(I2C_TypeDef* I2Cx, uint8_t *pBuf, uint32_t nbytes, uint8_t devAddr);
|
||||
|
||||
#endif // __I2C_H
|
||||
183
driver_fw/lcd1602.c
Normal file
183
driver_fw/lcd1602.c
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
This is free and unencumbered software released into the public domain.
|
||||
( https://github.com/KonstantinDM )
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
*/
|
||||
|
||||
#include "global.h"
|
||||
#include "i2c.h"
|
||||
#include "lcd1602.h"
|
||||
|
||||
static void write_byte(uint8_t in_u8Byte); // Отпвить байт на шину
|
||||
static void send_half_byte(uint8_t in_u8Byte); // Отправить пол байта
|
||||
static void send_command(uint8_t in_u8Byte); // Отправить байт команду
|
||||
static void delay_micro(uint32_t in_u8micros);
|
||||
|
||||
static uint8_t lcd_gpio = 0;
|
||||
|
||||
/*
|
||||
Инициализация дисплея, обязательнаяпроцедура
|
||||
на входе: *
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd1602_init()
|
||||
{
|
||||
// Инициализация экрана обязательна
|
||||
delay_micro(15000);
|
||||
send_half_byte(LCD_INITIALIZATION);
|
||||
delay_micro(4000);
|
||||
send_half_byte(LCD_INITIALIZATION);
|
||||
delay_micro(100000);
|
||||
send_half_byte(LCD_INITIALIZATION);
|
||||
delay_micro(1000);
|
||||
send_half_byte(LCD_SET_CURSOR_TO_START);
|
||||
|
||||
// Найстрока дисплея
|
||||
send_command(LCD_SET_INTERFACE_LINES_FONT | LCD_4BIT_INTERFACE | LCD_TWO_LINE | LCD_5x8_FONT);
|
||||
send_command(LCD_SET_CURSOR_AND_POWE_MODE | LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_CURSOR_BLINK_OFF);
|
||||
send_command(LCD_SET_SHIFT_AND_CHAR_DIRECTION | LCD_CHAR_DIRECTION_LEFT_RIGHT | LCD_SHIFT_DISABLE);
|
||||
|
||||
// Очистить экран
|
||||
lcd_clear();
|
||||
|
||||
// Включить подсветку и режим записи
|
||||
lcd_gpio |= LCD_MODE_LED;
|
||||
lcd_gpio &= ~LCD_MODE_WRITE;
|
||||
};
|
||||
|
||||
/*
|
||||
Отправить строку на экран с указанием позиции
|
||||
на входе: in_u8X - позиция символа в строке
|
||||
in_u8Y - номер строки
|
||||
in_cChar - символ для установки
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd_write_str(uint8_t in_u8X, uint8_t in_u8Y, char* in_cChar)
|
||||
{
|
||||
lcd_set_pos(in_u8X, in_u8Y);
|
||||
lcd_send_str(in_cChar);
|
||||
};
|
||||
|
||||
/*
|
||||
Установка курсора
|
||||
на входе: in_u8X - позиция символа в строке
|
||||
in_u8Y - номер строки
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd_set_pos(uint8_t in_u8X, uint8_t in_u8Y)
|
||||
{
|
||||
switch (in_u8Y) {
|
||||
case 0:
|
||||
send_command(in_u8X | LCD_SET_DDRAM_TO_ADDRESS);
|
||||
break;
|
||||
case 1:
|
||||
send_command((LCD_2_LINE_OFFSET + in_u8X) | LCD_SET_DDRAM_TO_ADDRESS);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка строки на экран
|
||||
на входе: in_cChar - указатель на строку
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd_send_str(char* in_pszChar)
|
||||
{
|
||||
char* l_pszChar = in_pszChar;
|
||||
while ((l_pszChar)[0])
|
||||
lcd_send_char((l_pszChar++)[0]);
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка символа на экран
|
||||
на входе: in_cChar - символ
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd_send_char(char in_cChar)
|
||||
{
|
||||
lcd_gpio |= LCD_MODE_DATA;
|
||||
send_half_byte(in_cChar >> 4);
|
||||
send_half_byte(in_cChar);
|
||||
};
|
||||
|
||||
/*
|
||||
Очистить экран
|
||||
на входе: *
|
||||
на выходе: *
|
||||
*/
|
||||
void lcd_clear()
|
||||
{
|
||||
send_command(LCD_SET_CLEAR);
|
||||
delay_micro(1530);
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка байта на шину
|
||||
на входе: in_u8Byte - байт с командой
|
||||
на выходе: *
|
||||
*/
|
||||
void write_byte(uint8_t in_u8Byte)
|
||||
{
|
||||
uint8_t buf[1] = { lcd_gpio | in_u8Byte };
|
||||
|
||||
i2c_transmit(LCD_I2C_PERIPH, buf, 1, LCD_I2C_ADDR, I2C_GENSTOP_YES);
|
||||
delay_micro(39);
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка половины байта экрану
|
||||
на входе: in_u8Byte - байт с командой
|
||||
на выходе: *
|
||||
*/
|
||||
void send_half_byte(uint8_t in_u8Byte)
|
||||
{
|
||||
write_byte(LCD_MODE_E_SET | (in_u8Byte << 4));
|
||||
write_byte(LCD_MODE_E_RESET);
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка команды дисплею
|
||||
на входе: in_u8Byte - байт с командой
|
||||
на выходе: *
|
||||
*/
|
||||
void send_command(uint8_t in_u8Byte)
|
||||
{
|
||||
lcd_gpio &= ~LCD_MODE_DATA;
|
||||
send_half_byte(in_u8Byte >> 4);
|
||||
send_half_byte(in_u8Byte);
|
||||
};
|
||||
|
||||
/*
|
||||
Отправка половины байта экрану
|
||||
на входе: in_u8Byte - байт с командой
|
||||
на выходе: *
|
||||
*/
|
||||
void delay_micro(uint32_t in_u8micros)
|
||||
{
|
||||
/* FIXME */
|
||||
in_u8micros *= (SystemCoreClock / 1000000) / 9;
|
||||
while (in_u8micros--)
|
||||
;
|
||||
};
|
||||
95
driver_fw/lcd1602.h
Normal file
95
driver_fw/lcd1602.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
This is free and unencumbered software released into the public domain.
|
||||
( https://github.com/KonstantinDM )
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
*/
|
||||
|
||||
#ifndef _C_LCD1602_H_INCLUDED_
|
||||
#define _C_LCD1602_H_INCLUDED_
|
||||
|
||||
#define LCD_I2C_PERIPH I2C1
|
||||
#define LCD_I2C_ADDR 0x4e
|
||||
|
||||
void lcd1602_init(); // Инициализация дисплея
|
||||
void lcd_write_str(uint8_t in_u8X, uint8_t in_u8Y, char* in_cChar); // Отправить строку на экран с указанием позиции
|
||||
void lcd_send_char(char in_cChar); // Отправить символ на экран
|
||||
void lcd_send_str(char* in_cChar); // Отправить строку на экран
|
||||
void lcd_set_pos(uint8_t in_u8X, uint8_t in_u8Y); // Установить позицию курсора
|
||||
void lcd_clear(); // Очистить экран
|
||||
|
||||
/*
|
||||
|P7|P6|P5|P4|P3|P2|P1|P0|
|
||||
|B7|B6|B5|B4|LED|E|RW|RS|
|
||||
*/
|
||||
|
||||
#define LCD_INITIALIZATION 0x03 // Инициализационный байт дисплея
|
||||
|
||||
#define LCD_MODE_COMMAND 0x00 // Флаг команды
|
||||
#define LCD_MODE_DATA 0x01 // Флаг данных
|
||||
#define LCD_MODE_LED 0x08 // флаг работы подсветки
|
||||
#define LCD_MODE_WRITE 0x02 // Флаг записи
|
||||
#define LCD_MODE_E_SET 0x04 // Флаг установки регистра Е
|
||||
#define LCD_MODE_E_RESET 0xFB // Флаг сброса регистра Е
|
||||
|
||||
#define LCD_2_LINE_OFFSET 0x40 // Адрес второй строки дисплея
|
||||
|
||||
#define LCD_SET_CLEAR 0x01 // (1.53ms) Очистка дисплея с установкой курсора в начало первой строки
|
||||
#define LCD_SET_CURSOR_TO_START 0x02 // (1.53ms) Установка курсора в начало первой строки
|
||||
|
||||
#define LCD_SET_SHIFT_AND_CHAR_DIRECTION 0x04 // (39mks) Установка направления вывода символов, разрешение сдвига экрана
|
||||
#define LCD_CHAR_DIRECTION_LEFT_RIGHT 0x02 // Вывод символов справа-налево, декремент адресного указателя DDRAM/CGRAM памяти
|
||||
#define LCD_CHAR_DIRECTION_RIGHT_LEFT 0x00 // Вывод символов слева-направо, инкремент адресного указателя DDRAM/CGRAM памяти
|
||||
#define LCD_SHIFT_DISABLE 0x00 // Запрет сдвига экрана при выводе символов
|
||||
#define LCD_SHIFT_ENABLE 0x01 // Разрешение сдвига экрана при выводе символов
|
||||
|
||||
#define LCD_SET_CURSOR_AND_POWE_MODE 0x08 // (39mks) Управление режимом питания дисплея и отображением курсора
|
||||
#define LCD_DISPLAY_OFF 0x00 // Выключить экран дисплея, сегменты погашены, содержимое внутренней памяти сохраняется
|
||||
#define LCD_DISPLAY_ON 0x04 // Включить экран дисплея, нормальный режим работы
|
||||
#define LCD_CURSOR_OFF 0x00 // Отключить отображение курсора
|
||||
#define LCD_CURSOR_ON 0x02 // Включить отображение курсора
|
||||
#define LCD_CURSOR_BLINK_OFF 0x00 // Отключить функцию мигания курсора
|
||||
#define LCD_CURSOR_BLINK_ON 0x01 // Включить функцию мигания курсора
|
||||
|
||||
#define LCD_SET_CURSOR_AND_DISPLAY_SHIFT 0x10 // (39mks) Команда сдвига курсора и экрана
|
||||
#define LCD_CURSOR_SHIFT 0x00 // Выбрать курсор для сдвига
|
||||
#define LCD_DISPLAY_AND_CURSOR_SHIFT 0x08 // Выбрать экран (вместе с курсором) для сдвига
|
||||
#define LCD_LEFT_SHIFT 0x00 // Сдвиг влево (только курсор или весь экран, зависит от бита S/C)
|
||||
#define LCD_RIGHT_SHIFT 0x04 // Сдвиг вправо (только курсор или весь экран, зависит от бита S/C)
|
||||
|
||||
#define LCD_SET_INTERFACE_LINES_FONT 0x20 // (39mks) Настройка интерфейса ввода/вывода данных, количества строк для вывода символов, размера шрифта
|
||||
#define LCD_4BIT_INTERFACE 0x00 // Сдвиг вправо (только курсор или весь экран, зависит от бита S/C)
|
||||
#define LCD_8BIT_INTERFACE 0x10 // 8-битный интерфейс ввода/вывода данных
|
||||
#define LCD_ONE_LINE 0x00 // Использовать одну строку для вывода символов
|
||||
#define LCD_TWO_LINE 0x08 // Задействовать 2 строки для вывода символов
|
||||
#define LCD_5x8_FONT 0x00 // Размер шрифта 5×8 пикселей
|
||||
#define LCD_5x11_FONT 0x04 // Размер шрифта 5×11 пикселей
|
||||
|
||||
#define LCD_SET_CGRAM_TO_ADDRESS 0x40 // (39mks) Запись адреса CGRAM памяти в адресный указатель
|
||||
#define LCD_CGRAM_TO_ADDRESS_MASK 0x3F // Маска байта данных
|
||||
|
||||
#define LCD_SET_DDRAM_TO_ADDRESS 0x80 // (39mks) Запись адреса DDRAM памяти в адресный указатель
|
||||
#define LCD_DDRAM_TO_ADDRESS_MASK 0x7F // Маска байта данных
|
||||
|
||||
#endif // _C_LCD1602_H_INCLUDED_
|
||||
3
driver_fw/mac.c
Normal file
3
driver_fw/mac.c
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "mac.h"
|
||||
|
||||
uint32_t device_mac = MAC_ADDR;
|
||||
22
driver_fw/mac.h
Normal file
22
driver_fw/mac.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef __MAC_H__
|
||||
#define __MAC_H__
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/* Device MAC address.
|
||||
*
|
||||
* 32 bits might seem a little short for a device MAC, but at 20 bus nodes the probablility of a collision is about 1 in
|
||||
* 10 million. Check for yourself using the python code below.
|
||||
*
|
||||
* #!/usr/bin/env python3
|
||||
* from operator import mul
|
||||
* from functools import reduce
|
||||
* m = 32
|
||||
* n = 20
|
||||
* print(reduce(mul, [2**m-i for i in range(n)]) / ((2**m)**n))
|
||||
* # -> 0.9999999557621786
|
||||
*/
|
||||
|
||||
extern uint32_t device_mac;
|
||||
|
||||
#endif /* __MAC_H__ */
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
#include "global.h"
|
||||
#include "serial.h"
|
||||
#include "i2c.h"
|
||||
#include "lcd1602.h"
|
||||
|
||||
#include <8b10b.h>
|
||||
|
||||
|
|
@ -102,6 +104,15 @@ int main(void) {
|
|||
|
||||
GPIOA->ODR = 0; /* Set PA4 ODR to 0 */
|
||||
|
||||
GPIOA->OTYPER |=
|
||||
GPIO_OTYPER_OT_1
|
||||
| GPIO_OTYPER_OT_2;
|
||||
|
||||
// FIXME lag 37.3us @ 720 Ohm / 16.0us @ 360 Ohm / 2.8us @ 88 Ohm
|
||||
GPIOA->OSPEEDR |=
|
||||
(3<<GPIO_OSPEEDR_OSPEEDR1_Pos)
|
||||
| (3<<GPIO_OSPEEDR_OSPEEDR2_Pos);
|
||||
|
||||
/* Note: since we have quite a bunch of pin constraints we can't actually use complementary outputs for the
|
||||
* complementary MOSFET driver control signals (CTRL_A & CTRL_B). Instead, we use two totally separate output
|
||||
* channels (1 & 4) and emulate the dead-time generator in software. */
|
||||
|
|
@ -112,7 +123,6 @@ int main(void) {
|
|||
|
||||
serial_init();
|
||||
/* FIXME ADC config */
|
||||
/* FIXME I2C config, drivers for LCD & current sensor */
|
||||
|
||||
/* SPI config. SPI1 is used to control the shift register controlling the eight status LEDs. */
|
||||
SPI1->CR2 = (7<<SPI_CR2_DS_Pos);
|
||||
|
|
@ -125,6 +135,16 @@ int main(void) {
|
|||
| SPI_CR1_MSTR;
|
||||
SPI1->CR1 |= SPI_CR1_SPE;
|
||||
|
||||
/* I2C for LCD, temp sensor, current sensor */
|
||||
i2c_config_filters(I2C1, I2C_AF_ENABLE, 0);
|
||||
i2c_config_timing(I2C1, 0x2000090e); /* Magic value for 100kHz I2C @ 48MHz CLK. Fell out of STMCubeMX. I love
|
||||
downloading 120MB of software to download another 100MB of software, only
|
||||
this time over unsecured HTTP, to generate 3.5 bytes of configuration values
|
||||
using a Java(TM) GUI. */
|
||||
i2c_enable(I2C1);
|
||||
lcd1602_init();
|
||||
lcd_write_str(0, 0, "Hello World!");
|
||||
|
||||
/* TIM3 is used to generate the MOSFET driver control signals */
|
||||
/* TIM3 running off 48MHz APB1 clk, T=20.833ns */
|
||||
TIM3->CR1 = 0; /* Disable ARR preload (double-buffering) */
|
||||
|
|
|
|||
233
driver_fw/serial.c
Normal file
233
driver_fw/serial.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "serial.h"
|
||||
#include "mac.h"
|
||||
|
||||
unsigned int uart_overruns = 0;
|
||||
unsigned int invalid_frames = 0;
|
||||
|
||||
static union tx_buf_union tx_buf;
|
||||
volatile union rx_buf_union rx_buf;
|
||||
|
||||
void serial_init() {
|
||||
USART1->CR1 = /* 8-bit -> M1, M0 clear */
|
||||
/* RTOIE clear */
|
||||
(8 << USART_CR1_DEAT_Pos) /* 8 sample cycles/1 bit DE assertion time */
|
||||
| (8 << USART_CR1_DEDT_Pos) /* 8 sample cycles/1 bit DE assertion time */
|
||||
/* OVER8 clear. Use default 16x oversampling */
|
||||
/* CMIF clear */
|
||||
| USART_CR1_MME
|
||||
/* WAKE clear */
|
||||
/* PCE, PS clear */
|
||||
| USART_CR1_RXNEIE /* Enable receive interrupt */
|
||||
/* other interrupts clear */
|
||||
| USART_CR1_TE
|
||||
| USART_CR1_RE;
|
||||
/* Invert TX and DE to accomodate the level shifters */
|
||||
USART1->CR2 = USART_CR2_TXINV;
|
||||
USART1->CR3 = USART_CR3_DEM | USART_CR3_DEP; /* enable RS485 DE (output on RTS) */
|
||||
/* Set divider for 9600 baud rate @48MHz system clock. */
|
||||
int usartdiv = 5000;
|
||||
USART1->BRR = usartdiv;
|
||||
|
||||
/* And... go! */
|
||||
USART1->CR1 |= USART_CR1_UE;
|
||||
|
||||
/* Enable receive interrupt */
|
||||
NVIC_EnableIRQ(USART1_IRQn);
|
||||
NVIC_SetPriority(USART1_IRQn, 1);
|
||||
}
|
||||
|
||||
void tx_char(uint8_t c) {
|
||||
while (!(USART1->ISR & USART_ISR_TC));
|
||||
USART1->TDR = c;
|
||||
}
|
||||
|
||||
void send_frame_formatted(uint8_t *buf, int len) {
|
||||
uint8_t *p=buf, *q=buf, *end=buf+len;
|
||||
do {
|
||||
while (*q && q!=end)
|
||||
q++;
|
||||
tx_char(q-p+1);
|
||||
while (*p && p!=end)
|
||||
tx_char(*p++);
|
||||
p++, q++;
|
||||
} while (p < end);
|
||||
tx_char('\0');
|
||||
}
|
||||
|
||||
void send_status_reply(void) {
|
||||
tx_buf.desc_reply.firmware_version = FIRMWARE_VERSION;
|
||||
tx_buf.desc_reply.hardware_version = HARDWARE_VERSION;
|
||||
tx_buf.desc_reply.pad[0] = tx_buf.desc_reply.pad[1] = 0;
|
||||
tx_buf.desc_reply.uptime_s = sys_time_s;
|
||||
//tx_buf.desc_reply.vcc_mv = adc_vcc_mv;
|
||||
//tx_buf.desc_reply.temp_celsius = adc_temp_celsius;
|
||||
tx_buf.desc_reply.global_brightness = global_brightness;
|
||||
tx_buf.desc_reply.framerate_millifps = frame_duration_us > 0 ? 1000000000 / frame_duration_us : 0;
|
||||
tx_buf.desc_reply.uart_overruns = uart_overruns;
|
||||
tx_buf.desc_reply.invalid_frames = invalid_frames;
|
||||
send_frame_formatted(tx_buf.byte_data, sizeof(tx_buf.desc_reply));
|
||||
}
|
||||
|
||||
/* This is the higher-level protocol handler for the serial protocol. It gets passed the number of data bytes in this
|
||||
* frame (which may be zero) and returns a pointer to the buffer where the next frame should be stored.
|
||||
*/
|
||||
volatile uint8_t *packet_received(int len) {
|
||||
static enum {
|
||||
PROT_ADDRESSED = 0,
|
||||
PROT_IGNORE = 2,
|
||||
} protocol_state = PROT_IGNORE;
|
||||
/* Use mac frames as delimiters to synchronize this protocol layer */
|
||||
trigger_comm_led();
|
||||
if (len == 0) { /* Discovery packet */
|
||||
if (sys_time_tick < 100) { /* Only respond during the first 100ms after boot */
|
||||
send_frame_formatted((uint8_t*)&device_mac, sizeof(device_mac));
|
||||
}
|
||||
|
||||
} else if (len == 1) { /* Command packet */
|
||||
if (protocol_state == PROT_ADDRESSED) {
|
||||
switch (rx_buf.byte_data[0]) {
|
||||
case 0x01:
|
||||
send_status_reply();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
invalid_frames++;
|
||||
trigger_error_led();
|
||||
}
|
||||
protocol_state = PROT_IGNORE;
|
||||
|
||||
} else if (len == 4) { /* Address packet */
|
||||
if (rx_buf.mac_data == device_mac) { /* we are addressed */
|
||||
protocol_state = PROT_ADDRESSED; /* start listening for frame buffer data */
|
||||
} else { /* we are not addressed */
|
||||
protocol_state = PROT_IGNORE; /* ignore packet */
|
||||
}
|
||||
|
||||
} else if (len == sizeof(rx_buf.set_fb_rq)/2) {
|
||||
if (protocol_state == PROT_ADDRESSED) { /* First of two half-framebuffer data frames */
|
||||
|
||||
/* FIXME */
|
||||
|
||||
/* Go to "hang mode" until next zero-length packet. */
|
||||
protocol_state = PROT_IGNORE;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* FIXME An invalid packet has been received. What should we do? */
|
||||
invalid_frames++;
|
||||
trigger_error_led();
|
||||
protocol_state = PROT_IGNORE; /* go into "hang mode" until next zero-length packet */
|
||||
}
|
||||
|
||||
/* By default, return rx_buf.byte_data . This means if an invalid protocol state is reached ("hang mode"), the next
|
||||
* frame is still written to rx_buf. This is not a problem since whatever garbage is written at that point will be
|
||||
* overwritten before the next buffer transfer. */
|
||||
return rx_buf.byte_data;
|
||||
}
|
||||
|
||||
void USART1_IRQHandler(void) {
|
||||
/* Since a large amount of data will be shoved down this UART interface we need a more reliable and more efficient
|
||||
* way of framing than just waiting between transmissions.
|
||||
*
|
||||
* This code uses "Consistent Overhead Byte Stuffing" (COBS). For details, see its Wikipedia page[0] or the proper
|
||||
* scientific paper[1] published on it. Roughly, it works like this:
|
||||
*
|
||||
* * A frame is at most 254 bytes in length.
|
||||
* * The null byte 0x00 acts as a frame delimiter. There is no null bytes inside frames.
|
||||
* * Every frame starts with an "overhead" byte indicating the number of non-null payload bytes until the next null
|
||||
* byte in the payload, **plus one**. This means this byte can never be zero.
|
||||
* * Every null byte in the payload is replaced by *its* distance to *its* next null byte as above.
|
||||
*
|
||||
* This means, at any point the receiver can efficiently be synchronized on the next frame boundary by simply
|
||||
* waiting for a null byte. After that, only a simple state machine is necessary to strip the overhead byte and a
|
||||
* counter to then count skip intervals.
|
||||
*
|
||||
* Here is Wikipedia's table of example values:
|
||||
*
|
||||
* Unencoded data Encoded with COBS
|
||||
* 00 01 01 00
|
||||
* 00 00 01 01 01 00
|
||||
* 11 22 00 33 03 11 22 02 33 00
|
||||
* 11 22 33 44 05 11 22 33 44 00
|
||||
* 11 00 00 00 02 11 01 01 01 00
|
||||
* 01 02 ...FE FF 01 02 ...FE 00
|
||||
*
|
||||
* [0] https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
|
||||
* [1] Cheshire, Stuart; Baker, Mary (1999). "Consistent Overhead Byte Stuffing"
|
||||
* IEEE/ACM Transactions on Networking. doi:10.1109/90.769765
|
||||
* http://www.stuartcheshire.org/papers/COBSforToN.pdf
|
||||
*/
|
||||
|
||||
/* This pointer stores where we write data. The higher-level protocol logic decides on a frame-by-frame-basis where
|
||||
* the next frame's data will be stored. */
|
||||
static volatile uint8_t *writep = rx_buf.byte_data;
|
||||
/* Index inside the current frame payload */
|
||||
static int rxpos = 0;
|
||||
/* COBS state machine. This implementation might be a little too complicated, but it works well enough and I find it
|
||||
* reasonably easy to understand. */
|
||||
static enum {
|
||||
COBS_WAIT_SYNC = 0, /* Synchronize with frame */
|
||||
COBS_WAIT_START = 1, /* Await overhead byte */
|
||||
COBS_RUNNING = 2 /* Process payload */
|
||||
} cobs_state = 0;
|
||||
/* COBS skip counter. During payload processing this contains the remaining non-null payload bytes */
|
||||
static int cobs_count = 0;
|
||||
|
||||
if (USART1->ISR & USART_ISR_ORE) { /* Overrun handling */
|
||||
uart_overruns++;
|
||||
trigger_error_led();
|
||||
/* Reset and re-synchronize. Retry next frame. */
|
||||
rxpos = 0;
|
||||
cobs_state = COBS_WAIT_SYNC;
|
||||
/* Clear interrupt flag */
|
||||
USART1->ICR = USART_ICR_ORECF;
|
||||
|
||||
} else { /* Data received */
|
||||
uint8_t data = USART1->RDR; /* This automatically acknowledges the IRQ */
|
||||
|
||||
if (data == 0x00) { /* End-of-packet */
|
||||
/* Process higher protocol layers on this packet. */
|
||||
writep = packet_received(rxpos);
|
||||
|
||||
/* Reset for next packet. */
|
||||
cobs_state = COBS_WAIT_START;
|
||||
rxpos = 0;
|
||||
|
||||
} else { /* non-null byte */
|
||||
if (cobs_state == COBS_WAIT_SYNC) { /* Wait for null byte */
|
||||
/* ignore data */
|
||||
|
||||
} else if (cobs_state == COBS_WAIT_START) { /* Overhead byte */
|
||||
cobs_count = data;
|
||||
cobs_state = COBS_RUNNING;
|
||||
|
||||
} else { /* Payload byte */
|
||||
if (--cobs_count == 0) { /* Skip byte */
|
||||
cobs_count = data;
|
||||
data = 0;
|
||||
}
|
||||
|
||||
/* Write processed payload byte to current receive buffer */
|
||||
writep[rxpos++] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
driver_fw/serial.h
Normal file
62
driver_fw/serial.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SERIAL_H__
|
||||
#define __SERIAL_H__
|
||||
|
||||
#include "global.h"
|
||||
|
||||
/* High-level stuff */
|
||||
void serial_init(void);
|
||||
void send_status_reply(void);
|
||||
|
||||
/* Internal low-level stuff */
|
||||
void tx_char(uint8_t c);
|
||||
void send_frame_formatted(uint8_t *buf, int len);
|
||||
volatile uint8_t *packet_received(int len);
|
||||
|
||||
/* Error counters for debugging */
|
||||
extern unsigned int uart_overruns;
|
||||
extern unsigned int invalid_frames;
|
||||
|
||||
union tx_buf_union {
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t firmware_version,
|
||||
hardware_version,
|
||||
pad[2];
|
||||
uint32_t uptime_s,
|
||||
framerate_millifps,
|
||||
uart_overruns,
|
||||
invalid_frames;
|
||||
int16_t vin_mv,
|
||||
v3v3_mv,
|
||||
iload_ma,
|
||||
temp_celsius;
|
||||
uint8_t global_brightness;
|
||||
} desc_reply;
|
||||
uint8_t byte_data[0];
|
||||
};
|
||||
|
||||
union rx_buf_union {
|
||||
struct __attribute__((packed)) { uint8_t fb[32]; uint8_t end[0]; } set_fb_rq;
|
||||
struct __attribute__((packed)) { uint8_t brightness; uint8_t end[0]; } set_global_brightness;
|
||||
uint8_t byte_data[0];
|
||||
uint32_t mac_data;
|
||||
};
|
||||
extern volatile union rx_buf_union rx_buf;
|
||||
|
||||
#endif/*__SERIAL_H__*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue