Compare commits

...

10 Commits

18 changed files with 179 additions and 101 deletions

View File

@@ -10,10 +10,9 @@
VERBOSE := false
ARCH := m1284
#FREQ := 18432000UL
FREQ := 8000000UL
MCU := atmega1284p
ASP := usbasp
FREQ := 18432000UL
ISP := usbasp
CC := avr-gcc
LD := $(CC)
OBJCOPY := avr-objcopy
@@ -23,6 +22,11 @@ MKDIR := mkdir -p
RMR := rm -rf
GIT := git
LFUSE := 0xF7
HFUSE := 0x97
EFUSE := 0xFF
LOCK := 0xFF
SRCDIR := src
BINDIR := bin
TMPDIR := $(BINDIR)/build
@@ -47,7 +51,7 @@ OBJECTS := $(FILES:%.c=$(TMPDIR)/%.o)
DEPENDS := $(FILES:%.c=$(TMPDIR)/%.d)
# ==============================================================================
# AUXILIARY TARGETS (AND FUSE SETTINGS)
# AUXILIARY TARGETS
# ==============================================================================
.PHONY: all
@@ -56,19 +60,34 @@ all: flash
.PHONY: flash
flash: $(TARGET)
$(E) "[AVD] Flashing..."
$(Q) $(AVD) -l $(LOGFILE) \
-c $(ASP) -p $(ARCH) \
-U lfuse:w:0xC2:m \
-U hfuse:w:0x97:m \
-U efuse:w:0xFF:m \
-U lock:w:0xFF:m \
$(Q) $(AVD) -l $(LOGFILE) \
-c $(ISP) -p $(ARCH) \
-U lfuse:w:$(LFUSE):m \
-U hfuse:w:$(HFUSE):m \
-U efuse:w:$(EFUSE):m \
-U lock:w:$(LOCK):m \
-U flash:w:$<
.PHONY: run
run: $(TARGET)
.PHONY: check
check: # Will be added at later stage
$(E) "[CHK] Not implemented."
$(Q) true
.PHONY: simulate
simulate: $(TARGET)
$(E) "[SIM] $<"
$(Q) $(SIM) -m $(MCU) -f $(FREQ) $<
.PHONY: listen
listen: opt/tools/serial-listen
$(E) "[RUN] $<"
$(Q) ./$<
.PHONY: webgui
webgui: opt/webgui/Makefile
$(E) "[MAK] $<"
$(Q) $(MAKE) -sC $(<D)
.PHONY: clean
clean:
$(E) "[REM] $(TARGET)"
@@ -85,21 +104,6 @@ distclean: clean
$(E) "[REM] $(BINDIR)"
$(Q) $(RMR) $(BINDIR)
.PHONY: check
check:
$(E) "[CHK] Not implemented."
$(Q) true
.PHONY: listen
listen: opt/tools/serial-listen
$(E) "[RUN] $<"
$(Q) ./$<
.PHONY: webgui
webgui: opt/webgui/Makefile
$(E) "[MAK] $<"
$(Q) $(MAKE) -sC $(<D)
$(TMPDIRS):
$(E) "[DIR] $@"
$(Q) $(MKDIR) $@

View File

@@ -1,11 +1,14 @@
## Changelog
### v0.75-alpha2
- Use internal oscillator without CKDIV8.
- Increase available memory to 4096 bytes.
- Use external 18.432 MHz crystal oscillator.
- Consolidate unfinished tasks and update documentation.
- Add papers describing PID controller implementation.
- Move fuse Makefile variables to general settings.
- Change ARCH and MCU settings to match chip signature.
- Update module 'pwm' to run on atmega1284p hardware.
- Update module 'usart' to run on atmega1284p hardware.
- Update module 'uart' to run on atmega1284p hardware.
- Update module 'watchdog' to run on atmega1284p hardware.
- Update module 'memory' to run on atmega1284p hardware.
- Update Makefile settings for new chip architecture.

View File

@@ -1,3 +1,5 @@
## License
All Rights Reserved
Copyright (c) 2024 Madcow Software

17
docs/PINOUT.md Normal file
View File

@@ -0,0 +1,17 @@
## Pinout Reference
The table below is a cross-reference for all active pins on
the microcontroller and their respective purpose:
| Pin | Name | Function |
| ---- | ----- | ------------------------- |
| PB0 | MOS01 | MOSFET Peltier enable |
| PB1 | MOS02 | MOSFET Heating enable |
| PB2 | MOS03 | MOSFET Lights enable |
| PC0 | TWSCL | I2C Primary data bus SCL |
| PC1 | TWSDA | I2C Primary data bus SDA |
| PD0 | UADRX | UART Debug interface RX |
| PD1 | UADTX | UART Debug interface TX |
| PD4 | FAN01 | PWM Fan peltier hot side |
| PD5 | FAN02 | PWM Fan peltier cold side |
| PD7 | FAN03 | PWM Fan heating element |

View File

@@ -31,21 +31,24 @@ Makefile for all build settings and make sure they are
correct for your current environment.
To remove build-related auxiliary files you may use one
of these commands:
of these commands, with varying levels of cleanliness:
make clean
make distclean
Isolated unit tests allow you to verify all testable
components are behaving as expected. Please note that most
tests will be added later down the road, when the project
has reached a more mature state.
Isolated unit tests allow you to verify all source modules
are behaving as expected. Please note that most tests will
be added later down the road, when the project has reached
a more mature state. There is also support for running the
binaries on simulated hardware if you have
[simavr](https://github.com/buserror/simavr) installed.
make check
make simulate
You can listen on the serial debug interface by running
the command below. This will also initialize all optional
submodules on first invocation.
You can listen on the serial debug interface by executing
the command below. Optional submodules will be initialized
on first invocation since they contain the necessary tools.
make listen

22
docs/TODO.md Normal file
View File

@@ -0,0 +1,22 @@
## Current Tasks
- Implement state machine for actual drying operation.
- Rewrite code documentation with proper structure.
- Check sensor measurements, conversion results and timer output frequencies.
- Implement custom bootloader to facilitate software updates over serial port.
- Make sure the initialization sequence can recover from as many technical
problems as possible.
- Implement I2C timeout or failing components may cause watchdog reset loop.
- Detect brown-out and external resets during the initialization sequence.
- Write isolated unit tests either with mocks or running in simulation.
- Write simulated I2C components for more realistic testing with simavr.
- MOSFET module needlessly specific? Rewrite as generic digital output?
- Handle more parser commands like UPDATE, START and STOP.
- Implement parser timeout for large input. Otherwise an UART RX buffer overflow
will swallow the line terminator leading to the next command not getting parsed
correctly. Also check for possible edge cases when the UART and parser buffers
have different sizes.
- Force system reset in Error() function?
- Test efficiency for I2C via interrupts and ADS1115 continous mode.
- Write cross-platform configuration header for different microchips?
- Refactoring and optimization after all v1.0 features are finalized.

BIN
docs/external/PIDC-01.pdf vendored Normal file

Binary file not shown.

BIN
docs/external/PIDC-02.pdf vendored Normal file

Binary file not shown.

BIN
docs/external/PIDC-03.pdf vendored Normal file

Binary file not shown.

View File

@@ -14,12 +14,6 @@
#define TW_MR_SLA_NACK 0x48 // SLA+R transmitted, NACK received
#define TW_MR_DATA_ACK 0x50 // Data received, ACK returned
// TODO: Error handling and recovery besides watchdog timer.
// TODO: Add more documentation from the atmel data sheet.
// TODO: ADS1115 continuous mode instead of single-shot?
// TODO: Implement TWI_vect ISR? This may not actually be
// much better than the blocking approach.
static void I2C_AHT20_Reset(void);
static bool I2C_AHT20_IsCalibrated(void);
static void I2C_AHT20_Calibrate(void);

View File

@@ -1,8 +1,6 @@
#include "common.h"
#include "bus/pwm.h"
// TODO: Add documentation for timer3: TCCR3A, TCCR3B, etc.
int PWM_Init(void)
{
// PD4: PWM NF-12 Fan Peltier Hot Side
@@ -102,15 +100,8 @@ void PWM_SetValue(int port, int value)
if (port != FAN01 && port != FAN02 && port != FAN03)
return; // Invalid port
// Workaround: Missing third 16-bit timer output
n = CLAMP(value, 100, 0) * PWM_CYCLE_TOP / 100.0f;
Info("Setting duty cycle for %s to %d/%d...",
(port == FAN01) ? "FAN01" :
(port == FAN02) ? "FAN02" :
(port == FAN03) ? "FAN03" :
"UNKNOWN", n, PWM_CYCLE_TOP);
switch (port) {
case PD4: OCR1B = n; break;
case PD5: OCR1A = n; break;

View File

@@ -1,24 +1,24 @@
#include "common.h"
#include "bus/usart.h"
#include "bus/uart.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#define USART_BAUDRATE 9600
#define USART_RXBUF_SIZE 128
#define USART_TXBUF_SIZE 128
#define UART_BAUDRATE 9600
#define UART_RXBUF_SIZE 128
#define UART_TXBUF_SIZE 128
#define USART_RXBUF_MASK (USART_RXBUF_SIZE - 1)
#define USART_TXBUF_MASK (USART_TXBUF_SIZE - 1)
#define USART_BAUD_PRESCALE ((((F_CPU / 16) + \
(USART_BAUDRATE / 2)) / (USART_BAUDRATE)) - 1)
#define UART_RXBUF_MASK (UART_RXBUF_SIZE - 1)
#define UART_TXBUF_MASK (UART_TXBUF_SIZE - 1)
#define UART_BAUD_PRESCALE ((((F_CPU / 16) + \
(UART_BAUDRATE / 2)) / (UART_BAUDRATE)) - 1)
static volatile char rxbuf[USART_RXBUF_SIZE]; // RX ring buffer
static volatile char txbuf[USART_TXBUF_SIZE]; // TX ring buffer
static volatile char rxbuf[UART_RXBUF_SIZE]; // RX ring buffer
static volatile char txbuf[UART_TXBUF_SIZE]; // TX ring buffer
static volatile short rxhead, txhead; // Current write position
static volatile short rxtail, txtail; // Current read position
int USART_Init(void)
int UART_Init(void)
{
rxhead = 0;
rxtail = 0;
@@ -28,29 +28,29 @@ int USART_Init(void)
UCSR0B = BIT(RXCIE0); // Handle RXC interrupts
UCSR0B |= BIT(RXEN0) | BIT(TXEN0); // Enable RX and TX circuitry
UCSR0C = BIT(UCSZ01) | BIT(UCSZ00); // 8-bit data, 1-bit stop, no parity
UBRR0H = (USART_BAUD_PRESCALE >> 8); // Set baud rate upper byte
UBRR0L = USART_BAUD_PRESCALE; // Set baud rate lower byte
UBRR0H = (UART_BAUD_PRESCALE >> 8); // Set baud rate upper byte
UBRR0L = UART_BAUD_PRESCALE; // Set baud rate lower byte
return 0;
}
char USART_Getc(void)
char UART_Getc(void)
{
if (rxhead == rxtail) {
return -1;
}
rxtail = (rxtail + 1) & USART_RXBUF_MASK;
rxtail = (rxtail + 1) & UART_RXBUF_MASK;
return rxbuf[rxtail];
}
void USART_Putc(char ch)
void UART_Putc(char ch)
{
short head;
// Wrap around if end of buffer reached
head = (txhead + 1) & USART_TXBUF_MASK;
head = (txhead + 1) & UART_TXBUF_MASK;
while (head == txtail); // Wait for space
txbuf[head] = ch;
@@ -69,7 +69,7 @@ ISR(USART0_RX_vect)
data = UDR0; // Next byte ready
// Wrap around if end of buffer reached
head = (rxhead + 1) & USART_RXBUF_MASK;
head = (rxhead + 1) & UART_RXBUF_MASK;
// Free space in RX buffer?
// Otherwise discard overflow
@@ -87,7 +87,7 @@ ISR(USART0_UDRE_vect)
// Anything in TX buffer?
if (txhead != txtail) {
// Write next byte to data register
tail = (txtail + 1) & USART_TXBUF_MASK;
tail = (txtail + 1) & UART_TXBUF_MASK;
UDR0 = txbuf[tail];
txtail = tail;
} else {

8
src/bus/uart.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef MAD_CORE_BUS_UART_H
#define MAD_CORE_BUS_UART_H
int UART_Init(void);
void UART_Putc(char ch);
char UART_Getc(void);
#endif // MAD_CORE_BUS_UART_H

View File

@@ -1,8 +0,0 @@
#ifndef MAD_CORE_BUS_USART_H
#define MAD_CORE_BUS_USART_H
int USART_Init(void);
void USART_Putc(char ch);
char USART_Getc(void);
#endif // MAD_CORE_BUS_USART_H

View File

@@ -1,5 +1,5 @@
#include "common.h"
#include "bus/usart.h"
#include "bus/uart.h"
#include <stdio.h>
@@ -42,7 +42,7 @@ void Error(const char *fmt, ...)
static void Puts(const char *str)
{
while (*str != '\0') {
USART_Putc(*str++);
UART_Putc(*str++);
}
}

View File

@@ -6,11 +6,6 @@
#define CMD_MAX_LEN 128
// TODO: Write documentation.
// TODO: Reset command buffer on timeout.
// TODO: Test with different RXBUF sizes.
// TODO: Add commands update, start and stop.
static char cmdbuf[CMD_MAX_LEN + 1];
static char *tail = cmdbuf + CMD_MAX_LEN;
static char *head = cmdbuf;

View File

@@ -1,21 +1,23 @@
#include "common.h"
#include "bus/usart.h"
#include "bus/uart.h"
#include "bus/mosfet.h"
#include "bus/pwm.h"
#include "bus/i2c.h"
#include <avr/interrupt.h>
// TODO: Facilitate software updates over serial port.
// TODO: Check parser and circular buffer for edge cases.
// TODO: Implement command parser timeout for large input.
// TODO: Config header for chip specifics like EEPROM size.
// TODO: Check thermistor conversion results /w thermometer.
// TODO: Implement primary state machine for update loop.
// TODO: Use 18.432MHz quarz crystal, burn required fuses.
// TODO: Implement optional CRC8 sensor measurement check.
// TODO: Proper error handling and recovery (after testing).
// TODO: Check why the MCUCSR EXTRF reset flag is set.
#define DEADBAND 2.0f
#define HYSTERESIS_C 0.5f
#define HYSTERESIS_H 0.5f
// https://en.wikipedia.org/wiki/Bang%E2%80%93bang_control
// https://support.75f.io/hc/en-us/articles/360044956794-Deadband-and-Hysteresis
// https://www.elotech.de/fileadmin/user_upload/PID-Regelungsgrundlagen.pdf
// https://thomasfermi.github.io/Algorithms-for-Automated-Driving/Control/PID.html
// https://www.ni.com/en/shop/labview/pid-theory-explained.html
// https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller
// https://ctms.engin.umich.edu/CTMS/index.php?example=Introduction&section=ControlPID
// https://www.youtube.com/watch?v=wkfEZmsQqiA&list=PLn8PRpmsu08pQBgjxYFXSsODEF3Jqmm-y
enum state_e
{
@@ -52,7 +54,7 @@ static int Init(void)
// IRQs, so we need to initialize it as soon as
// possible and make sure to enable interrupts.
USART_Init();
UART_Init();
sei();
Info("Initializing...");
@@ -132,9 +134,11 @@ static void Update(void)
{
char ch;
cmd_t cmd;
float terr;
float derr;
// Parse serial commands
while ((ch = USART_Getc()) >= 0) {
while ((ch = UART_Getc()) >= 0) {
if (!CMD_Parse(ch, &cmd)) {
continue;
}
@@ -148,15 +152,53 @@ static void Update(void)
// Get latest sensor values
FetchSensorValues();
// Handle state
// Two-step (bang-bang) controller testing
// TODO: Sanity check setpoint!
// TODO: Check thermistor for overheating!
// TODO: Implement control stages based on hysteresis
// TODO: PID control for fan pulse-width modulation?
// The dead band represents the lower and upper limits
// of the error between which the controller doesnt
// react.
state = S_IDLE;
terr = temp - temp_target;
derr = dewp - dewp_target;
if (terr < -DEADBAND / 2) {
state = S_HEAT_UP;
}
if (terr > DEADBAND / 2) {
state = S_COOL_DOWN;
}
switch (state) {
case S_IDLE:
MOS_Disable(MOS01);
MOS_Disable(MOS02);
PWM_SetValue(FAN01, 20);
PWM_SetValue(FAN02, 20);
PWM_SetValue(FAN03, 20);
break;
case S_HEAT_UP:
MOS_Enable(MOS02);
MOS_Disable(MOS01);
PWM_SetValue(FAN01, 20);
PWM_SetValue(FAN02, 60);
PWM_SetValue(FAN03, 60);
break;
case S_COOL_DOWN:
MOS_Enable(MOS01);
MOS_Disable(MOS02);
PWM_SetValue(FAN01, 60);
PWM_SetValue(FAN02, 60);
PWM_SetValue(FAN03, 60);
break;
case S_DEHUMIDIFY:
UNUSED(derr);
break;
}
}
@@ -224,6 +266,11 @@ static void FetchSensorValues(void)
Info("T3=%.2fC, TD3=%.2fC, RH3=%.2f%%, NT3=%.2fC", t[2], td[2], rh[2], t[5]);
Info("T_AVG=%.2fC, TD_AVG=%.2fC, RH_AVG=%.2f%%", temp, dewp, rhum);
Info("T_TAR=%.2fC, TD_TAR=%.2fC", temp_target, dewp_target);
Info("STATE=%s", (state == S_IDLE) ? "S_IDLE" :
(state == S_HEAT_UP) ? "S_HEAT_UP" :
(state == S_COOL_DOWN) ? "S_COOL_DOWN" :
(state == S_DEHUMIDIFY) ? "S_DEHUMIDIFY" :
"UNKNOWN");
}
int main(void)