feat(firmware): add GPIO expander and keypad drivers
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# Firmware Debug
|
||||
|
||||
## Purpose
|
||||
|
||||
This document describes the debugging methods used during the development of the OpenParcelBox firmware.
|
||||
|
||||
The objective is to provide simple diagnostic tools that work even when a debugger is unavailable.
|
||||
|
||||
---
|
||||
|
||||
# Debug Methods
|
||||
|
||||
The firmware currently supports the following debugging methods:
|
||||
|
||||
- UART console (`printf`)
|
||||
- On-board RGB LED
|
||||
- Zephyr Shell (when enabled)
|
||||
|
||||
Additional methods may be added later:
|
||||
|
||||
- Zigbee diagnostics
|
||||
- Home Assistant diagnostic entities
|
||||
- NFC diagnostics
|
||||
|
||||
---
|
||||
|
||||
# UART Console
|
||||
|
||||
The UART console is the primary debugging interface during firmware development.
|
||||
|
||||
Example:
|
||||
|
||||
```c
|
||||
printf("Initialization completed.\n");
|
||||
printf("Key pressed: %c\n", key);
|
||||
```
|
||||
|
||||
UART should be preferred whenever a serial connection is available.
|
||||
|
||||
---
|
||||
|
||||
# RGB LED Status Codes
|
||||
|
||||
The onboard RGB LED is used to quickly identify the firmware state.
|
||||
|
||||
## Startup
|
||||
|
||||
| Color | Meaning |
|
||||
| ------- | ----------------------------------- |
|
||||
| Red | Firmware initialization in progress |
|
||||
| Green | Firmware initialized successfully |
|
||||
| Magenta | Initialization failed |
|
||||
|
||||
---
|
||||
|
||||
## Runtime
|
||||
|
||||
| Color | Meaning |
|
||||
| ------- | ------------------------- |
|
||||
| Green | Idle / Ready |
|
||||
| Blue | User interaction detected |
|
||||
| Yellow | Warning |
|
||||
| Cyan | Reserved |
|
||||
| White | Reserved |
|
||||
| Red | Reserved |
|
||||
| Magenta | Fatal error |
|
||||
|
||||
---
|
||||
|
||||
# Current Diagnostic Sequence
|
||||
|
||||
Current startup sequence:
|
||||
|
||||
1. Initialize RGB LED
|
||||
2. Turn LED Red
|
||||
3. Initialize peripherals
|
||||
4. If initialization fails:
|
||||
- Turn LED Magenta
|
||||
- Stop execution
|
||||
5. Turn LED Green
|
||||
6. Start main application loop
|
||||
|
||||
---
|
||||
|
||||
# UART Examples
|
||||
|
||||
Keypad:
|
||||
|
||||
```text
|
||||
Key pressed: 1
|
||||
Key pressed: 5
|
||||
Key pressed: #
|
||||
```
|
||||
|
||||
Future lock module:
|
||||
|
||||
```text
|
||||
Opening lock...
|
||||
Lock opened.
|
||||
```
|
||||
|
||||
Future NFC module:
|
||||
|
||||
```text
|
||||
NFC tag detected.
|
||||
UID: xx xx xx xx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Zephyr Shell
|
||||
|
||||
The Zephyr Shell is enabled during development.
|
||||
|
||||
Typical commands:
|
||||
|
||||
```text
|
||||
uart:~$ i2c scan i2c@40004000
|
||||
```
|
||||
|
||||
This allows verification of:
|
||||
|
||||
- I²C devices
|
||||
- GPIO configuration
|
||||
- Driver availability
|
||||
|
||||
The shell is intended for development only and may be disabled in production builds.
|
||||
|
||||
---
|
||||
|
||||
# Production Firmware
|
||||
|
||||
The production firmware should not rely on the UART console.
|
||||
|
||||
Diagnostics should instead use:
|
||||
|
||||
- RGB LED
|
||||
- Zigbee diagnostic messages
|
||||
- Home Assistant entities
|
||||
- Error reporting through the application
|
||||
|
||||
---
|
||||
|
||||
# Future Improvements
|
||||
|
||||
Planned debugging features:
|
||||
|
||||
- Configurable log levels
|
||||
- Persistent error codes
|
||||
- Diagnostic mode
|
||||
- Self-test during startup
|
||||
- Hardware validation report
|
||||
- Watchdog diagnostics
|
||||
|
||||
---
|
||||
|
||||
# Notes
|
||||
|
||||
Debugging features should remain lightweight to minimize firmware size and power consumption.
|
||||
|
||||
Development-only diagnostics should be removable without affecting the application logic.
|
||||
@@ -0,0 +1,82 @@
|
||||
# GPIO Expander
|
||||
|
||||
## Purpose
|
||||
|
||||
This module provides a hardware abstraction layer (HAL) for the external GPIO expander.
|
||||
|
||||
The current implementation targets the PCF8574 connected over the I²C bus. Other GPIO expanders (PCF8575, MCP23017, etc.) can later be supported by modifying only this module.
|
||||
|
||||
All higher-level modules (keypad, lock, etc.) must use this API instead of directly accessing the I²C bus.
|
||||
|
||||
---
|
||||
|
||||
## Development Hardware
|
||||
|
||||
- Seeed Studio XIAO nRF52840
|
||||
- PCF8574
|
||||
- I²C address: `0x20`
|
||||
|
||||
---
|
||||
|
||||
## Public API
|
||||
|
||||
```c
|
||||
int gpio_expander_init(void);
|
||||
|
||||
int gpio_expander_read_port(uint8_t *value);
|
||||
|
||||
int gpio_expander_write_port(uint8_t value);
|
||||
|
||||
bool gpio_expander_read_pin(uint8_t pin);
|
||||
|
||||
int gpio_expander_write_pin(uint8_t pin, bool state);
|
||||
|
||||
int gpio_expander_update_port(uint8_t mask, uint8_t value);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Goals
|
||||
|
||||
- Hide the hardware implementation.
|
||||
- Avoid direct I²C access from application modules.
|
||||
- Allow replacing the GPIO expander without modifying the rest of the firmware.
|
||||
|
||||
---
|
||||
|
||||
## Current Implementation
|
||||
|
||||
- PCF8574
|
||||
- 8 GPIO
|
||||
- Quasi-bidirectional outputs
|
||||
- I²C communication
|
||||
|
||||
---
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Possible future implementations:
|
||||
|
||||
- PCF8575
|
||||
- MCP23008
|
||||
- MCP23017
|
||||
|
||||
Only `gpio_expander.c` should require modifications.
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
- Keypad
|
||||
- Future lock module (if additional GPIO are required)
|
||||
- Future peripherals connected through an external GPIO expander
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
The PCF8574 uses quasi-bidirectional GPIOs.
|
||||
|
||||
A pin must be written HIGH before it can be used as an input.
|
||||
|
||||
This behavior is fully handled by this module and should remain transparent to higher-level drivers.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Keypad
|
||||
|
||||
## Purpose
|
||||
|
||||
This module manages the matrix keypad used by OpenParcelBox.
|
||||
|
||||
The current implementation targets the development hardware based on a Freenove 4x4 keypad connected through a PCF8574 I²C GPIO expander.
|
||||
|
||||
The production hardware will use a custom 2x6 keypad. The scanning algorithm will remain identical. Only the hardware mapping and key table will change.
|
||||
|
||||
---
|
||||
|
||||
## Development Hardware
|
||||
|
||||
- Seeed Studio XIAO nRF52840
|
||||
- Freenove 4x4 Matrix Keypad
|
||||
- PCF8574 GPIO Expander
|
||||
- I²C address: `0x20`
|
||||
|
||||
---
|
||||
|
||||
## Wiring
|
||||
|
||||
| PCF8574 | Function |
|
||||
| ------- | -------- |
|
||||
| P0 | Row 1 |
|
||||
| P1 | Row 2 |
|
||||
| P2 | Row 3 |
|
||||
| P3 | Row 4 |
|
||||
| P4 | Column 1 |
|
||||
| P5 | Column 2 |
|
||||
| P6 | Column 3 |
|
||||
| P7 | Column 4 |
|
||||
|
||||
---
|
||||
|
||||
## Key Mapping
|
||||
|
||||
| | Col 1 | Col 2 | Col 3 | Col 4 |
|
||||
| ----- | ----- | ----- | ----- | ----- |
|
||||
| Row 1 | 1 | 2 | 3 | A |
|
||||
| Row 2 | 4 | 5 | 6 | B |
|
||||
| Row 3 | 7 | 8 | 9 | C |
|
||||
| Row 4 | * | 0 | # | D |
|
||||
|
||||
---
|
||||
|
||||
## Public API
|
||||
|
||||
```c
|
||||
int keypad_init(void);
|
||||
|
||||
char keypad_get_key(void);
|
||||
```
|
||||
|
||||
`keypad_get_key()` returns:
|
||||
|
||||
- `0` if no key is available.
|
||||
- The ASCII value of the pressed key.
|
||||
|
||||
Each key press is reported only once.
|
||||
|
||||
---
|
||||
|
||||
## Current Features
|
||||
|
||||
- Matrix scanning
|
||||
- Debounce
|
||||
- Single key detection
|
||||
- Anti-repeat
|
||||
- Modular GPIO Expander interface
|
||||
|
||||
---
|
||||
|
||||
## Planned Improvements
|
||||
|
||||
- Support for the production 2x6 keypad.
|
||||
- Configurable key mapping.
|
||||
- Optional audible feedback handled by the buzzer module.
|
||||
- Unit tests.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
@@ -6,5 +6,7 @@ project(OpenParcelBox)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/board.c
|
||||
src/led.c
|
||||
src/gpio_expander.c
|
||||
src/keypad.c
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
*
|
||||
* Development board configuration.
|
||||
*/
|
||||
|
||||
&i2c1 {
|
||||
status = "okay";
|
||||
};
|
||||
+45
-11
@@ -1,18 +1,52 @@
|
||||
# Contraintes application
|
||||
# Firmware Constraints
|
||||
|
||||
## Technologies
|
||||
|
||||
- Développement avec Flutter.
|
||||
- Compatibilité Android prioritaire.
|
||||
- Compatibilité iOS souhaitée mais non bloquante pour la V1.
|
||||
- Development with Zephyr RTOS.
|
||||
- Programming language: C17.
|
||||
- Target board: Seeed Studio XIAO nRF52840.
|
||||
- Build system: CMake + West.
|
||||
- All source code comments must be written in English.
|
||||
- The firmware must compile without warnings whenever possible.
|
||||
|
||||
## Fonctionnement
|
||||
## Architecture
|
||||
|
||||
- Utilisation locale prioritaire.
|
||||
- Interface simple pour les utilisateurs non techniques.
|
||||
- Modular architecture.
|
||||
- One module per hardware peripheral or functional block.
|
||||
- Hardware abstraction must be separated from business logic.
|
||||
- All GPIO assignments must be centralized in `board_config.h`.
|
||||
- Hardware initialization must be centralized in the board module.
|
||||
|
||||
## Fonctionnalités
|
||||
## Hardware
|
||||
|
||||
- Gestion des accès.
|
||||
- Gestion des codes.
|
||||
- Consultation de l'état de la boîte.
|
||||
- Battery-powered operation.
|
||||
- Minimize power consumption whenever possible.
|
||||
- GPIO assignments must match the hardware documentation.
|
||||
- DeviceTree must be used for hardware configuration whenever possible.
|
||||
- I²C peripherals must use Zephyr drivers.
|
||||
|
||||
## Features
|
||||
|
||||
- Matrix keypad management.
|
||||
- Electronic lock control.
|
||||
- Lock state monitoring.
|
||||
- PWM buzzer control.
|
||||
- PCF8574 I/O expander support.
|
||||
- NFC support.
|
||||
- Zigbee communication.
|
||||
- Battery level monitoring.
|
||||
- LED status management.
|
||||
|
||||
## Development Rules
|
||||
|
||||
- Develop one hardware module at a time.
|
||||
- Each module must compile successfully before starting the next one.
|
||||
- Validate each hardware feature on the development board before integration.
|
||||
- Avoid unused source files.
|
||||
- Keep the project structure clean and scalable.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Every public function must be documented.
|
||||
- Complex algorithms must include explanatory comments in English.
|
||||
- Keep the firmware synchronized with the project documentation.
|
||||
@@ -1,10 +1,7 @@
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_PWM=y
|
||||
|
||||
CONFIG_SERIAL=y
|
||||
CONFIG_I2C_SHELL=y
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_CONSOLE=y
|
||||
|
||||
CONFIG_LOG=y
|
||||
CONFIG_LOG_MODE_IMMEDIATE=y
|
||||
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||
CONFIG_SERIAL=y
|
||||
CONFIG_UART_CONSOLE=y
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Board pin definitions.
|
||||
*/
|
||||
|
||||
#ifndef BOARD_PINS_H
|
||||
#define BOARD_PINS_H
|
||||
|
||||
#include <zephyr/devicetree.h>
|
||||
|
||||
/* RGB LED */
|
||||
|
||||
#define LED_RED_NODE DT_ALIAS(led0)
|
||||
#define LED_GREEN_NODE DT_ALIAS(led1)
|
||||
#define LED_BLUE_NODE DT_ALIAS(led2)
|
||||
|
||||
/* GPIO Expander */
|
||||
|
||||
#define GPIO_EXPANDER_I2C_NODE DT_NODELABEL(i2c1)
|
||||
#define GPIO_EXPANDER_ADDRESS 0x20
|
||||
|
||||
#endif /* BOARD_PINS_H */
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* GPIO expander abstraction layer.
|
||||
*
|
||||
* Current implementation:
|
||||
* PCF8574 connected on I2C.
|
||||
*/
|
||||
|
||||
#include "gpio_expander.h"
|
||||
|
||||
#include "board_pins.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Static variables
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static const struct device *i2c_dev = DEVICE_DT_GET(GPIO_EXPANDER_I2C_NODE);
|
||||
|
||||
/*
|
||||
* Current GPIO output state.
|
||||
*
|
||||
* The PCF8574 uses quasi-bidirectional GPIOs.
|
||||
* Pins intended to be used as inputs must remain HIGH.
|
||||
*/
|
||||
static uint8_t gpio_state = 0xFF;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Public API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
int gpio_expander_init(void) {
|
||||
if (!device_is_ready(i2c_dev)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gpio_state = 0xFF;
|
||||
|
||||
return gpio_expander_write_port(gpio_state);
|
||||
}
|
||||
|
||||
int gpio_expander_read_port(uint8_t *value) {
|
||||
if (value == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return i2c_read(i2c_dev, value, sizeof(*value), GPIO_EXPANDER_ADDRESS);
|
||||
}
|
||||
|
||||
int gpio_expander_write_port(uint8_t value) {
|
||||
gpio_state = value;
|
||||
|
||||
return i2c_write(i2c_dev, &gpio_state, sizeof(gpio_state),
|
||||
GPIO_EXPANDER_ADDRESS);
|
||||
}
|
||||
|
||||
bool gpio_expander_read_pin(uint8_t pin) {
|
||||
uint8_t value;
|
||||
|
||||
if (pin > 7U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gpio_expander_read_port(&value) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (value & (1U << pin)) != 0U;
|
||||
}
|
||||
|
||||
int gpio_expander_write_pin(uint8_t pin, bool state) {
|
||||
if (pin > 7U) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
gpio_state |= (1U << pin);
|
||||
} else {
|
||||
gpio_state &= ~(1U << pin);
|
||||
}
|
||||
|
||||
return gpio_expander_write_port(gpio_state);
|
||||
}
|
||||
|
||||
int gpio_expander_update_port(uint8_t mask, uint8_t value) {
|
||||
gpio_state &= ~mask;
|
||||
gpio_state |= (value & mask);
|
||||
|
||||
return gpio_expander_write_port(gpio_state);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* GPIO expander abstraction layer.
|
||||
*/
|
||||
|
||||
#ifndef GPIO_EXPANDER_H
|
||||
#define GPIO_EXPANDER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize the GPIO expander.
|
||||
*
|
||||
* Initializes the external GPIO expander used by the firmware.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Initialization failed.
|
||||
*/
|
||||
int gpio_expander_init(void);
|
||||
|
||||
/**
|
||||
* @brief Read the complete GPIO port.
|
||||
*
|
||||
* @param[out] value Pointer receiving the GPIO state.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Read failed.
|
||||
*/
|
||||
int gpio_expander_read_port(uint8_t *value);
|
||||
|
||||
/**
|
||||
* @brief Write the complete GPIO port.
|
||||
*
|
||||
* @param value GPIO output state.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Write failed.
|
||||
*/
|
||||
int gpio_expander_write_port(uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief Read a single GPIO pin.
|
||||
*
|
||||
* @param pin GPIO pin number.
|
||||
*
|
||||
* @retval true Pin is HIGH.
|
||||
* @retval false Pin is LOW or an error occurred.
|
||||
*/
|
||||
bool gpio_expander_read_pin(uint8_t pin);
|
||||
|
||||
/**
|
||||
* @brief Write a single GPIO pin.
|
||||
*
|
||||
* @param pin GPIO pin number.
|
||||
* @param state Desired pin state.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Write failed.
|
||||
*/
|
||||
int gpio_expander_write_pin(uint8_t pin, bool state);
|
||||
|
||||
/**
|
||||
* @brief Update selected GPIO pins.
|
||||
*
|
||||
* Only the bits specified by the mask are modified.
|
||||
*
|
||||
* @param mask GPIO selection mask.
|
||||
* @param value New value for selected pins.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Write failed.
|
||||
*/
|
||||
int gpio_expander_update_port(uint8_t mask, uint8_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GPIO_EXPANDER_H */
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Module:
|
||||
* Keypad driver
|
||||
*
|
||||
* Description:
|
||||
* Driver for the 4x4 matrix keypad used during development.
|
||||
*
|
||||
* Development hardware:
|
||||
* - Freenove 4x4 Matrix Keypad
|
||||
* - PCF8574 I2C GPIO Expander
|
||||
* - I2C address: 0x20
|
||||
*
|
||||
* Wiring:
|
||||
*
|
||||
* Columns
|
||||
* C1 C2 C3 C4
|
||||
* PCF8574 P4 P5 P6 P7
|
||||
*
|
||||
* Rows
|
||||
* P0
|
||||
* P1
|
||||
* P2
|
||||
* P3
|
||||
*
|
||||
* Future:
|
||||
* The production OpenParcelBox keypad (2x6) will reuse this driver.
|
||||
* Only the row/column mapping and key table will change.
|
||||
*/
|
||||
|
||||
#include "keypad.h"
|
||||
|
||||
#include "gpio_expander.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#define KEYPAD_ROW_COUNT 4
|
||||
#define KEYPAD_COLUMN_COUNT 4
|
||||
|
||||
#define KEYPAD_DEBOUNCE_MS 20
|
||||
|
||||
static const uint8_t row_pins[KEYPAD_ROW_COUNT] = {0, 1, 2, 3};
|
||||
|
||||
static const uint8_t column_pins[KEYPAD_COLUMN_COUNT] = {4, 5, 6, 7};
|
||||
|
||||
static const char keymap[KEYPAD_ROW_COUNT][KEYPAD_COLUMN_COUNT] = {
|
||||
{'1', '2', '3', 'A'},
|
||||
{'4', '5', '6', 'B'},
|
||||
{'7', '8', '9', 'C'},
|
||||
{'*', '0', '#', 'D'}};
|
||||
|
||||
static char last_key = 0;
|
||||
|
||||
/**
|
||||
* @brief Scan the keypad matrix.
|
||||
*
|
||||
* @return Detected ASCII key or 0.
|
||||
*/
|
||||
static char keypad_scan(void) {
|
||||
uint8_t value;
|
||||
|
||||
for (uint8_t row = 0; row < KEYPAD_ROW_COUNT; row++) {
|
||||
|
||||
gpio_expander_write_port(0xFF);
|
||||
|
||||
gpio_expander_write_pin(row_pins[row], false);
|
||||
|
||||
if (gpio_expander_read_port(&value) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t column = 0; column < KEYPAD_COLUMN_COUNT; column++) {
|
||||
|
||||
if ((value & (1U << column_pins[column])) == 0U) {
|
||||
|
||||
return keymap[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int keypad_init(void) {
|
||||
last_key = 0;
|
||||
|
||||
return gpio_expander_init();
|
||||
}
|
||||
|
||||
char keypad_get_key(void) {
|
||||
char key;
|
||||
|
||||
key = keypad_scan();
|
||||
|
||||
if (key == 0) {
|
||||
last_key = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
k_msleep(KEYPAD_DEBOUNCE_MS);
|
||||
|
||||
if (key != keypad_scan()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (key == last_key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
last_key = key;
|
||||
|
||||
return key;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*/
|
||||
|
||||
#ifndef KEYPAD_H
|
||||
#define KEYPAD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the keypad driver.
|
||||
*
|
||||
* Initializes the GPIO expander and prepares the keypad driver.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Initialization failed.
|
||||
*/
|
||||
int keypad_init(void);
|
||||
|
||||
/**
|
||||
* @brief Read a key from the keypad.
|
||||
*
|
||||
* Performs a keypad scan and returns a single key press.
|
||||
* The same key is returned only once until it is released.
|
||||
*
|
||||
* @return ASCII character corresponding to the pressed key.
|
||||
* @return 0 if no key is available.
|
||||
*/
|
||||
char keypad_get_key(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* KEYPAD_H */
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* RGB LED driver.
|
||||
*/
|
||||
|
||||
#include "led.h"
|
||||
|
||||
#include "board_pins.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
|
||||
static const struct gpio_dt_spec led_red_gpio =
|
||||
GPIO_DT_SPEC_GET(LED_RED_NODE, gpios);
|
||||
|
||||
static const struct gpio_dt_spec led_green_gpio =
|
||||
GPIO_DT_SPEC_GET(LED_GREEN_NODE, gpios);
|
||||
|
||||
static const struct gpio_dt_spec led_blue_gpio =
|
||||
GPIO_DT_SPEC_GET(LED_BLUE_NODE, gpios);
|
||||
|
||||
/**
|
||||
* @brief Set the RGB LED state.
|
||||
*
|
||||
* @param red Red LED state.
|
||||
* @param green Green LED state.
|
||||
* @param blue Blue LED state.
|
||||
*/
|
||||
static void led_set_rgb(bool red, bool green, bool blue) {
|
||||
gpio_pin_set_dt(&led_red_gpio, red);
|
||||
gpio_pin_set_dt(&led_green_gpio, green);
|
||||
gpio_pin_set_dt(&led_blue_gpio, blue);
|
||||
}
|
||||
|
||||
int led_init(void) {
|
||||
int ret;
|
||||
|
||||
if (!gpio_is_ready_dt(&led_red_gpio) || !gpio_is_ready_dt(&led_green_gpio) ||
|
||||
!gpio_is_ready_dt(&led_blue_gpio)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&led_red_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&led_green_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&led_blue_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
led_off();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void led_off(void) { led_set_rgb(false, false, false); }
|
||||
|
||||
void led_set_red(void) { led_set_rgb(true, false, false); }
|
||||
|
||||
void led_set_green(void) { led_set_rgb(false, true, false); }
|
||||
|
||||
void led_set_blue(void) { led_set_rgb(false, false, true); }
|
||||
|
||||
void led_set_yellow(void) { led_set_rgb(true, true, false); }
|
||||
|
||||
void led_set_cyan(void) { led_set_rgb(false, true, true); }
|
||||
|
||||
void led_set_magenta(void) { led_set_rgb(true, false, true); }
|
||||
|
||||
void led_set_white(void) { led_set_rgb(true, true, true); }
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* RGB LED driver.
|
||||
*/
|
||||
|
||||
#ifndef LED_H
|
||||
#define LED_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the RGB LED driver.
|
||||
*
|
||||
* Configures the three onboard RGB LED GPIOs.
|
||||
*
|
||||
* @retval 0 Success.
|
||||
* @retval <0 Initialization failed.
|
||||
*/
|
||||
int led_init(void);
|
||||
|
||||
/**
|
||||
* @brief Turn off the RGB LED.
|
||||
*/
|
||||
void led_off(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED red.
|
||||
*/
|
||||
void led_set_red(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED green.
|
||||
*/
|
||||
void led_set_green(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED blue.
|
||||
*/
|
||||
void led_set_blue(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED yellow.
|
||||
*/
|
||||
void led_set_yellow(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED cyan.
|
||||
*/
|
||||
void led_set_cyan(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED magenta.
|
||||
*/
|
||||
void led_set_magenta(void);
|
||||
|
||||
/**
|
||||
* @brief Turn the RGB LED white.
|
||||
*/
|
||||
void led_set_white(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LED_H */
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Main firmware entry point.
|
||||
*
|
||||
* Current test:
|
||||
* - RGB LED
|
||||
* - GPIO Expander
|
||||
* - 4x4 Keypad
|
||||
*/
|
||||
|
||||
#include "keypad.h"
|
||||
#include "led.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Defines
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#define MAIN_LOOP_DELAY_MS 20
|
||||
#define KEY_PRESS_LED_MS 100
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Public API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
int main(void) {
|
||||
char key;
|
||||
|
||||
if (led_init() < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
led_set_red();
|
||||
k_msleep(1000);
|
||||
|
||||
if (keypad_init() < 0) {
|
||||
led_set_magenta();
|
||||
|
||||
while (1) {
|
||||
k_msleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
led_set_green();
|
||||
|
||||
printf("\n");
|
||||
printf("========================================\n");
|
||||
printf("OpenParcelBox Firmware\n");
|
||||
printf("Hardware Test: RGB LED + Keypad\n");
|
||||
printf("========================================\n");
|
||||
|
||||
led_set_green();
|
||||
|
||||
while (1) {
|
||||
|
||||
key = keypad_get_key();
|
||||
|
||||
if (key != 0) {
|
||||
|
||||
printf("Key pressed: %c\n", key);
|
||||
|
||||
led_set_blue();
|
||||
|
||||
k_msleep(KEY_PRESS_LED_MS);
|
||||
|
||||
led_set_green();
|
||||
}
|
||||
|
||||
k_msleep(MAIN_LOOP_DELAY_MS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user