Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions can/network.yml
Original file line number Diff line number Diff line change
Expand Up @@ -805,3 +805,12 @@ nodes:
- bar:
description: Bar.
width: 40
- BATT:
messages:
- BatteryStatus:
id: 0x12C
cycletime: 10
signals:
- batteryPercent:
description: Battery percentage.
width: 10
1 change: 1 addition & 0 deletions components/SConscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'steer',
'sup',
'throttle',
'batt',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to keep this list in alphabetical order.

]
]
)
Expand Down
16 changes: 16 additions & 0 deletions components/batt/Sconscript.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ruff: noqa: F821

Import('env')
Import('envs')


firmware, flash = env.SConscript(
'src/SConscript.py',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the SConscript.py under src/?

exports={'env': envs['esp32s3']},
)

component, name = env.Component(firmware, env.File('component.toml'))
env.ComponentSubtarget(name, 'flash', flash)


Return('component')
3 changes: 3 additions & 0 deletions components/batt/component.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[metadata]
name = 'batt'
description = 'battery-monitor'
39 changes: 39 additions & 0 deletions components/batt/src/SConscript.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ruff: noqa: F821

Import('env')

node = 'BATT'

opencan = env.OpenCan(
network=env['CAN']['NETWORK'],
node=node,
)

source = [
env.StaticObject(
src,
CPPDEFINES=[
('EMBER_NODE_IDENTITY', node),
'$CPPDEFINES',
],
CPPPATH=[
env.Dir(opencan[0].dir.name),
'$CPPPATH',
],
)
for src in [
'batt.c', # Ensure this file exists in your source directory
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'batt.c', # Ensure this file exists in your source directory
'batt.c',

*env['LIBRARIES']['firmware-base'],
]
]

# Correct the typo in the variable name
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't you just fix it?

source += opencan
source += env['LIBRARIES']['ember']
source += env['LIBRARIES']['node-entry']
source += env['LIBRARIES']['selfdrive']

batt = env.StaticLibrary(node.lower(), source)[0]
firmware, flash = env.EspIdf(batt, 'esp32s3')

Return('firmware', 'flash')
26 changes: 26 additions & 0 deletions components/batt/src/batt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "batt.h"

#include <ember_taskglue.h>
#include <opencan_rx.h>
#include <opencan_tx.h>

#include <math.h>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include only the headers you need.


static uint8_t batteryPercent;

static void batt_1Hz();

ember_rate_funcs_S module_rf = {
.call_1Hz = batt_1Hz,
};

static void batt_1Hz()
{
batteryPercent = 0;
}

void CANTX_populate_BatteryStatus(
struct CAN_Message_BATT_BatteryStatus * const m)
{
m->BATT_batteryPercent = batteryPercent;
}
4 changes: 4 additions & 0 deletions components/batt/src/batt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#ifndef BATT_H
#define BATT_H

#endif // BATT.H
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#endif // BATT.H
#endif /* BATT_H */

20 changes: 20 additions & 0 deletions components/batt/src/firmware-base/SConscript.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ruff: noqa: F821

Import('env')


# since this has to be compiled uniquely for each micro we'll leave this
# as strings so that we can symlink this directory and turn these into
# File() nodes
firmware_base = [
f'firmware-base/{src}'
for src in [
'app-description.c',
'eeprom.c',
'eeprom_ember.c',
'state-machine.c',
]
]


Return('firmware_base')
12 changes: 12 additions & 0 deletions components/batt/src/firmware-base/app-description.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <ember_app_desc.h>

// stringification macros
#define XSTR(s) STR(s)
#define STR(s) #s

const IN_DESC_SECTION ember_app_desc_v1_t ember_app_description = {
.ember_magic = EMBER_MAGIC,
.app_desc_version = EMBER_APP_DESC_VERSION,

.node_identity = XSTR(EMBER_NODE_IDENTITY),
};
240 changes: 240 additions & 0 deletions components/batt/src/firmware-base/eeprom.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
#include "eeprom.h"

#include <driver/gpio.h>
#include <driver/i2c.h>
#include <esp_err.h>
#include <node_pins.h>

#include <stdint.h>

#define EEPROM_PAGESIZ 64
#define EEPROM_ADDR_I2C (0xa0 | (0x00 << 1))
#define EEPROM_ADDR_MAX 0x7fff
#define EEPROM_SCL_GPIO NODE_BOARD_PIN_EEPROM_SCL
#define EEPROM_SDA_GPIO NODE_BOARD_PIN_EEPROM_SDA
#define EEPROM_WP_GPIO NODE_BOARD_PIN_EEPROM_WP
#define EEPROM_WP(ENABLE) gpio_set_level(EEPROM_WP_GPIO, ENABLE)

#define I2C_ESP_INTR_FLAGS 0
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_NUM 0
#define I2C_MASTER_RX_BUF 0
#define I2C_MASTER_TX_BUF 0
#define I2C_TICK_TIMEOUT 0
#define I2C_WRITE 0
#define I2C_READ 1

static esp_err_t sel_read(uint16_t addr);

static uint16_t internal_addr;

void eeprom_init()
{
gpio_set_direction(EEPROM_WP_GPIO, GPIO_MODE_OUTPUT);
EEPROM_WP(true);

i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EEPROM_SDA_GPIO,
.scl_io_num = EEPROM_SCL_GPIO,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};

i2c_param_config(I2C_MASTER_NUM, &conf);
i2c_driver_install(I2C_MASTER_NUM,
I2C_MODE_MASTER,
I2C_MASTER_RX_BUF,
I2C_MASTER_TX_BUF,
I2C_ESP_INTR_FLAGS);

// we want to know where we are in EEPROM
// in case of an immediate address read
// the following sets internal_addr to 0
uint8_t tmp;
eeprom_read(EEPROM_ADDR_MAX, &tmp, 1);
}

// sel_read attempts a selective read at the given address.
// can be used for acknowledge polling (to ensure EEPROM is not busy)
static esp_err_t sel_read(uint16_t addr)
{
i2c_cmd_handle_t cmd;
uint8_t buf[2];
uint8_t data[1];
esp_err_t val = ESP_OK;

buf[0] = addr >> 8;
buf[1] = addr & 0xff;

cmd = i2c_cmd_link_create();
if (!cmd) return ESP_ERR_NO_MEM;

i2c_master_start(cmd);
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_WRITE, true);
i2c_master_write(cmd, buf, sizeof(buf), true);
i2c_master_start(cmd); // Missing end signal is intentional
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_READ, true);
i2c_master_read(cmd, data, 1, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);

val = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, I2C_TICK_TIMEOUT);
i2c_cmd_link_delete(cmd);

return val;
}

esp_err_t eeprom_read(uint16_t addr, uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd;
uint8_t buf[2];
esp_err_t val = ESP_OK;

// mask MSB of the address to ensure
// we can compare w/ internal_addr
addr &= EEPROM_ADDR_MAX;

buf[0] = addr >> 8;
buf[1] = addr & 0xff;

// ensure we can read
while (true) {
val = sel_read(internal_addr);
if (val == ESP_OK) break;

// when the EEPROM is busy writing it will
// not send an ACK, returning ESP_FAIL
if (val != ESP_FAIL) goto error;
}

cmd = i2c_cmd_link_create();
if (!cmd) return ESP_ERR_NO_MEM;

if (internal_addr != addr) { // selective read at the given address
i2c_master_start(cmd);
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_WRITE, true);
i2c_master_write(cmd, buf, sizeof(buf), true);
// missing end signal is intentional
} // otherwise, do an immediate access read (address not required)

i2c_master_start(cmd);
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_READ, true);
i2c_master_read(cmd, data, len, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);

val = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, I2C_TICK_TIMEOUT);
i2c_cmd_link_delete(cmd);

internal_addr = addr + len;
internal_addr &= EEPROM_ADDR_MAX;

error:
return val;
}

esp_err_t eeprom_write(uint16_t addr, const uint8_t *data, size_t len)
{
i2c_cmd_handle_t cmd;
uint16_t page;
uint8_t offset, write_len;
uint8_t buf[2];
esp_err_t val = ESP_OK;

while (len) {
// mask MSB of the address to ensure correct calculations
addr &= EEPROM_ADDR_MAX;

page = addr / EEPROM_PAGESIZ;
offset = addr % EEPROM_PAGESIZ;
buf[0] = page >> 2;
buf[1] = ((page & 0x03) << 6) | offset;

// we don't want to write too much
// since we can only write maximum one page at at time
write_len = EEPROM_PAGESIZ - offset;
if (write_len > len) write_len = len;

// ensure we can write
while (true) {
val = sel_read(internal_addr);
if (val == ESP_OK) break;

// when the EEPROM is busy writing it will
// not send an ACK, returning ESP_FAIL
if (val != ESP_FAIL) goto error;
}

cmd = i2c_cmd_link_create();
if (!cmd) return ESP_ERR_NO_MEM;

// WP is always enabled after sel_read() call
EEPROM_WP(false);

i2c_master_start(cmd);
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_WRITE, true);
i2c_master_write(cmd, buf, sizeof(buf), true);
i2c_master_write(cmd, data, write_len, true);
i2c_master_stop(cmd);

val = i2c_master_cmd_begin(
I2C_MASTER_NUM, cmd, I2C_TICK_TIMEOUT);
i2c_cmd_link_delete(cmd);
if (val != ESP_OK) goto error;

addr += write_len;
internal_addr = addr;
internal_addr &= EEPROM_ADDR_MAX;

data += write_len;
len -= write_len;
}

error:
EEPROM_WP(true);

return val;
}

esp_err_t eeprom_write_byte(uint16_t addr, const uint8_t data)
{
i2c_cmd_handle_t cmd;
uint8_t buf[3];
esp_err_t val = ESP_OK;

// ensure we can write
while (true) {
val = sel_read(internal_addr);
if (val == ESP_OK) break;

// when the EEPROM is busy writing it will
// not send an ACK, returning ESP_FAIL
if (val != ESP_FAIL) goto error;
}

buf[0] = addr >> 8;
buf[1] = addr & 0xff;
buf[2] = data;

cmd = i2c_cmd_link_create();
if (!cmd) return ESP_ERR_NO_MEM;

// WP is always enabled after sel_read() call
EEPROM_WP(false);

i2c_master_start(cmd);
i2c_master_write_byte(cmd, EEPROM_ADDR_I2C | I2C_WRITE, true);
i2c_master_write(cmd, buf, sizeof(buf), true);
i2c_master_stop(cmd);

val = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, I2C_TICK_TIMEOUT);
i2c_cmd_link_delete(cmd);

EEPROM_WP(true);

internal_addr = addr + 1;
internal_addr &= EEPROM_ADDR_MAX;

error:
return val;
}
Loading