fw: drivers: add AS7000 HRM driver code/demo app

This commit is contained in:
Liam McLoughlin 2025-02-13 11:40:38 +00:00
parent f19ace2e3c
commit 5b5d49cb49
15 changed files with 1809 additions and 6 deletions

View file

@ -0,0 +1,130 @@
Some Information About the AMS AS7000
=====================================
The documentation about the AS7000 can be a bit hard to follow and a bit
incomplete at times. This document aims to fill in the gaps, using
knowledge gleaned from the datasheet, Communication Protocol document
and the SDK docs and sources.
What is the AS7000 exactly?
---------------------------
The AS7000 is a Cortex-M0 SoC with some very specialized peripherals.
It can be programmed with a firmware to have it function as a heart-rate
monitor. It doesn't necessarily come with the HRM firmware preloaded, so
it is not one of those devices that you can treat as a black-box piece
of hardware that you just power up and talk to.
There is 32 kB of flash for the main application, and some "reserved"
flash containing a loader application. There is also a ROM first-stage
bootloader.
Boot Process
------------
The chip is woken from shutdown by pulling the chip's GPIO8 pin low. The
CPU core executes the ROM bootloader after powerup or wake from
shutdown. The bootloader's logic is apparently as follows:
if loader is available:
jump to loader
elif application is valid:
remap flash to address 0
jump to application
else:
wait on UART?? (not documented further)
We will probably always be using parts with the loader application
preprogrammed by AMS so the existence of the bootloader just makes the
communication protocol document a bit more confusing to understand. It
can be safely ignored and we can pretend that the loader application is
the only bootloader.
Once control is passed from the ROM bootloader to the loader, the loader
performs the following:
wait 30 ms
if application is valid and GPIO8 is low:
remap flash to address 0
jump to application
else:
run loader application
The reason for the 30 ms wait and check for GPIO8 is to provide an
escape hatch for getting into the loader if a misbehaving application
is programmed which doesn't allow for a command to be used to enter the
loader.
The "Application is valid" check is apparently to check that address
0x7FFC (top of main flash) contains the bytes 72 75 6C 75.
The Loader
----------
The loader allows new applications to be programmed into the main flash
over I2C by sending Intel HEX records(!). The communication protocol
document describes the protocol well enough, though it is a bit lacking
in detail as to what HEX records it accepts. Luckily the SDK comes with
a header file which fills in some of the details, and the SDK Getting
Started document has some more details.
- The supported record types are 00 (Data), 01 (EOF), 04 (Extended
Linear Address) and 05 (Start Linear Address).
- The HEX records must specify addresses in the aliased range 0-0x7FFF
- HEX records must be sent in order of strictly increasing addresses.
A comment in loader.h from the SDK states that the maximum record size
supported by the loader can be configured as 256, 128, 64 or 32. The
`#define` in the same header which sets the max record length is set
to 256, strongly implying that this is the record length limit for the
loader firmware which is already programmed into the parts. Programming
an application firmware is successful when using records of length 203,
which seems to confirm that the max record length is indeed 256.
The HEX files provided by AMS appear to be standard HEX files generated
by the Keil MDK-ARM toolchain. http://www.keil.com/support/docs/1584.htm
The Start Linear Address record contains the address of the "pre-main"
function, which is not the reset vector. Since the reset vector is
already written as data in the application's vector table, the record is
unnecessary for flash loading and is likely just thrown away by the
loader.
Empirical testing has confirmed that neither the Start Linear Address
nor Extended Linear Address records need to be sent for flashing to
succeed. It is acceptable to send only a series of Data records followed
by an EOF record.
The loader will exit if one of the following conditions is true:
- An EOF record is sent
- A valid HEX record is sent which the loader doesn't understand
- Data is sent which is not a valid HEX record
- No records are sent for approximately ten seconds
The loader exits by waiting one second for the host controller to read
out the exit code register, then performs a system reset.
The Application
---------------
Due to the simplicity of the CM0 and the limited resources available on
the chip, the application firmware takes full control of the system. The
applications in the SDK are written as a straightforward mainloop with
no OS. Think of it as a very simple form of cooperative multitasking.
The "mandatory" I2C register map mentioned in the Communication Protocol
document is merely a part of the protocol; it is entirely up to the
application firmware to properly implement it. The Application ID
register doesn't do anything special on its own: the mainloop simply
polls the register value at each iteration to see if it needs to start
or stop any "apps" (read: modules). The application firmware could
misbehave, resulting in writes to that register having no effect.
Writing 0x01 to the Application ID register isn't special either; it is
up to the application to recognize that value and reset into the loader.
That's why the escape hatch is necessary, and the fundamental reason why
the loader application cannot run at the same time as any other
application.
GPIO8 isn't special while the firmware is running, either.

1120
src/fw/drivers/hrm/as7000.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -48,3 +48,16 @@ typedef const struct HRMDevice {
OutputConfig en_gpio;
I2CSlavePort *i2c_slave;
} HRMDevice;
typedef struct PACKED AS7000InfoRecord {
uint8_t protocol_version_major;
uint8_t protocol_version_minor;
uint8_t sw_version_major;
uint8_t sw_version_minor;
uint8_t application_id;
uint8_t hw_revision;
} AS7000InfoRecord;
//! Fills a struct which contains version info about the AS7000
//! This should probably only be used by the HRM Demo app
void as7000_get_version_info(HRMDevice *dev, AS7000InfoRecord *info_out);

View file

@ -1174,4 +1174,18 @@ if mcu_family in ('STM32F2', 'STM32F4', 'STM32F7'):
],
)
if bld.get_hrm() == 'AS7000':
bld.objects(
name='driver_hrm',
source=[
'hrm/as7000.c',
],
use=[
'driver_gpio',
'driver_i2c',
'fw_includes',
'stm32_stlib'
],
)
# vim:filetype=python