Compare commits

..

15 Commits

Author SHA1 Message Date
5248fa2562 Set license to undetermined for now 2026-02-02 20:04:01 +00:00
65cda87e7d Banish all the ambiguity that has arisen from unicode characters in datasheets 2026-02-02 19:37:55 +00:00
3da596f2b6 Further unicode disambiguation 2026-02-02 19:31:28 +00:00
4e0b6b34a8 Fix ambiguous unicode character 2026-02-02 19:29:51 +00:00
43efdfc2f4 Remove directory opt 2026-02-02 06:45:18 +01:00
3320788caf Update tools submodule for generating temperature graphs 2024-10-06 20:28:27 +02:00
d45581b5b8 Fix semantics of HYSTERESIS constant and calculate error for dewpoint 2024-10-05 19:24:55 +02:00
334ce3fafb Test naive bang-bang control algorithm for temperature 2024-10-04 12:57:10 +02:00
ce6bc8a8a9 Add papers describing PID controller implementation for self-regulating systems 2024-10-04 05:38:23 +02:00
eea8c015c0 Use external 18.432 MHz crystal oscillator with full swing fuse settings 2024-10-02 19:39:50 +02:00
481bf3aa70 Consolidate unfinished tasks and update documentation 2024-10-02 07:19:22 +02:00
8d94bd87a2 Rename USART module to UART to emphasize asynchronous mode operation 2024-10-02 06:07:24 +02:00
7133f54617 Move fuse variables to general settings and minor naming fixes 2024-10-01 22:04:22 +02:00
f1262d099b Add pinout reference table and license heading 2024-10-01 20:16:41 +02:00
295c5b1ccf Rename hardware simulation target and reference in README 2024-10-01 17:33:04 +02:00
21 changed files with 184 additions and 114 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,15 +1,11 @@
## License
All Rights Reserved
Copyright (c) 2024 Madcow Software
Copyright (c) 2024 Madcow Software
Copyright (c) 2024 Carlos Krieg
THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL.
UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE
CONTENTS OF THIS PROJECT, VIA ANY MEDIUM IS STRICTLY PROHIBITED.
The receipt or possession of the source code and/or any parts
thereof does not convey or imply any right to use them for any
purpose other than the purpose for which they were provided to you.
LICENSE UNDETERMINED FOR NOW
The software is provided "AS IS", without warranty of any kind,
express or implied, including but not limited to the warranties of
@@ -21,4 +17,4 @@ connection with the software or the use or other dealings in the
software.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the software.
included in all copies or substantial portions of the 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.

Submodule opt/tools deleted from ebe9e06e50

Submodule opt/webgui deleted from 2e1b57767c

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

@@ -226,7 +226,7 @@ static void WriteRaw(int addr, byte data)
// Wait until ready
while (EECR & BIT(EEPE));
// The EEPROM Address Registers EEARH and
// The EEPROM Address Registers - EEARH and
// EEARL specify the EEPROM address in the
// 512/1K/2K/4Kbytes EEPROM space. The EEPROM
// data bytes are addressed linearly between 0

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

@@ -97,7 +97,7 @@ bool WDT_HasTriggered(void)
// which reset source caused an MCU reset.
// 7 6 5 4 3 2 1 0
// - - JTRF WDRF BORF EXTRF PORF
// - - - JTRF WDRF BORF EXTRF PORF
// Is watchdog reset flag set?
isreset = ((MCUSR & BIT(WDRF)) != 0);
@@ -128,7 +128,7 @@ void WDT_Disable(void)
// See macro in watchdog.h
void WDT_Reset(void)
{
// The WDR Watchdog Reset instruction resets the
// The WDR - Watchdog Reset - instruction resets the
// watchdog timer. The Watchdog Timer is also reset
// when it is disabled and when a chip reset occurs.

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 doesn't
// 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)