Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ target_link_libraries(${PROJECT_NAME}
pico_stdlib
hardware_adc
hardware_dma
hardware_pwm
)

pico_add_extra_outputs(${PROJECT_NAME})
Expand Down
115 changes: 99 additions & 16 deletions Firmware/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <hardware/gpio.h>
#include <hardware/adc.h>
#include <hardware/dma.h>
#include <pico/util/queue.h>
#include <hardware/pwm.h>

// Create device name array.
const uint16_t who_am_i = 123;
Expand All @@ -26,6 +28,9 @@ const uint32_t AI0_PIN = 26;
const uint32_t AI1_PIN = 27;
const uint32_t AI2_PIN = 28;
const uint32_t AI_MASK = 0x7;
const uint32_t PWM_PIN = 0;
const uint32_t PWM_SLICE = pwm_gpio_to_slice_num(PWM_PIN);
const uint32_t PWM_CHANNEL = pwm_gpio_to_channel(PWM_PIN);

// Harp App state.
bool events_active = false;
Expand All @@ -51,22 +56,35 @@ const int32_t adc_period_us = 4000;
const int32_t adc_callback_delay_us = 80000;
int adc_sample_channel;
int adc_ctrl_channel;
static queue_t adc_queue;

// Define queue item contents
#pragma pack(push, 1)
struct adc_queue_item_t
{
uint64_t timestamp;
uint16_t analog_data[3];
};
#pragma pack(pop)
adc_queue_item_t adc_queue_current;

// Harp App Register Setup.
const size_t reg_count = 8;
const size_t reg_count = 10;

// Define register contents.
#pragma pack(push, 1)
struct app_regs_t
{
volatile uint8_t di_state;
volatile uint8_t do_set;
volatile uint8_t do_clear;
volatile uint8_t do_toggle;
volatile uint8_t do_state;
volatile uint32_t start_pulse_train[4];
volatile uint8_t stop_pulse_train;
volatile uint16_t analog_data[3];
volatile uint8_t di_state; // 32
volatile uint8_t do_set; // 33
volatile uint8_t do_clear; // 34
volatile uint8_t do_toggle; // 35
volatile uint8_t do_state; // 36
volatile uint32_t start_pulse_train[4]; // 37
volatile uint8_t stop_pulse_train; //38
volatile uint16_t analog_data[3]; // 39
volatile uint32_t pwm_config[2]; // 40 [0]=frequency in Hz, [1]=duty cycle (0-100)
volatile uint8_t pwm_stop; // 41
} app_regs;
#pragma pack(pop)

Expand All @@ -80,7 +98,9 @@ RegSpecs app_reg_specs[reg_count]
{(uint8_t*)&app_regs.do_state, sizeof(app_regs.do_state), U8},
{(uint8_t*)&app_regs.start_pulse_train, sizeof(app_regs.start_pulse_train), U32},
{(uint8_t*)&app_regs.stop_pulse_train, sizeof(app_regs.stop_pulse_train), U8},
{(uint8_t*)&app_regs.analog_data, sizeof(app_regs.analog_data), U16}
{(uint8_t*)&app_regs.analog_data, sizeof(app_regs.analog_data), U16},
{(uint8_t*)&app_regs.pwm_config, sizeof(app_regs.pwm_config), U32},
{(uint8_t*)&app_regs.pwm_stop, sizeof(app_regs.pwm_stop), U8}
};

void gpio_callback(uint gpio, uint32_t events)
Expand Down Expand Up @@ -203,14 +223,50 @@ bool adc_callback(repeating_timer_t *rt)
rt->delay_us = -adc_period_us;

// Mask the values to 12 bits (0xFFF) to ensure only valid ADC bits are used
app_regs.analog_data[0] = adc_vals[0] & 0xFFF;
app_regs.analog_data[1] = adc_vals[1] & 0xFFF;
app_regs.analog_data[2] = adc_vals[2] & 0xFFF;

HarpCore::send_harp_reply(EVENT, APP_REG_START_ADDRESS + 7);
adc_queue_item_t item;
item.timestamp = HarpCore::harp_time_us_64();
item.analog_data[0] = adc_vals[0] & 0xFFF;
item.analog_data[1] = adc_vals[1] & 0xFFF;
item.analog_data[2] = adc_vals[2] & 0xFFF;
queue_add_blocking(&adc_queue, &item);
return true;
}

void write_pwm_config(msg_t &msg)
{
HarpCore::copy_msg_payload_to_register(msg);

uint32_t frequency = app_regs.pwm_config[0];
uint32_t duty_percent = app_regs.pwm_config[1];

if (duty_percent > 100) duty_percent = 100;

// Assume 125MHz for the 2040
float clock_div = 125.0f;
uint32_t wrap = 1000000 / frequency; // At 1MHz, this gives us cycles per PWM period
uint32_t level = (wrap * duty_percent) / 100;

pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv(&config, clock_div);
pwm_config_set_wrap(&config, wrap - 1); // Wrap is 0-based

pwm_init(PWM_SLICE, &config, true);
pwm_set_chan_level(PWM_SLICE, PWM_CHANNEL, level);

HarpCore::send_harp_reply(WRITE, msg.header.address);
}

void write_pwm_stop(msg_t &msg)
{
HarpCore::copy_msg_payload_to_register(msg);

pwm_set_enabled(PWM_SLICE, false);
gpio_put(PWM_PIN, false);

HarpCore::send_harp_reply(WRITE, msg.header.address);
}


// Define register read-and-write handler functions.
RegFnPair reg_handler_fns[reg_count]
{
Expand All @@ -221,7 +277,9 @@ RegFnPair reg_handler_fns[reg_count]
{&HarpCore::read_reg_generic, &write_do_state},
{&HarpCore::read_reg_generic, &write_start_pulse_train},
{&HarpCore::read_reg_generic, &write_stop_pulse_train},
{&HarpCore::read_reg_generic, &HarpCore::write_to_read_only_reg_error}
{&HarpCore::read_reg_generic, &HarpCore::write_to_read_only_reg_error},
{&HarpCore::read_reg_generic, &write_pwm_config},
{&HarpCore::read_reg_generic, &write_pwm_stop}
};

void app_reset()
Expand All @@ -239,6 +297,13 @@ void app_reset()
app_regs.analog_data[0] = 0;
app_regs.analog_data[1] = 0;
app_regs.analog_data[2] = 0;
app_regs.pwm_config[0] = 1000;
app_regs.pwm_config[1] = 50;
app_regs.pwm_stop = 0;

pwm_set_enabled(PWM_SLICE, false);
gpio_put(PWM_PIN, false);

}

void configure_gpio(void)
Expand All @@ -248,6 +313,8 @@ void configure_gpio(void)
gpio_set_dir_in_masked(DI_MASK);
gpio_clr_mask(DO_MASK);

gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);

gpio_set_irq_callback(gpio_callback);
gpio_set_irq_enabled(2, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);
gpio_set_irq_enabled(3, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);
Expand Down Expand Up @@ -324,6 +391,10 @@ void configure_adc(void)
1, // Number of word transfers.
false // Don't Start immediately.
);

// Configure queue for storing sample data and avoid concurrency in
// outbound message buffers, i.e. avoid sending reply in timer callback.
queue_init(&adc_queue, sizeof(adc_queue_item_t), 2);
}

void enable_adc_events()
Expand Down Expand Up @@ -375,12 +446,23 @@ void update_app_state()
}
else if (events_active && !HarpCore::events_enabled())
{
// disable PWM
pwm_set_enabled(PWM_SLICE, false);
gpio_put(PWM_PIN, false);
// disable events
enable_gpio(false);
disable_adc_events();
cancel_pulse_timers();
events_active = false;
}

if (events_active && queue_try_remove(&adc_queue, &adc_queue_current))
{
app_regs.analog_data[0] = adc_queue_current.analog_data[0];
app_regs.analog_data[1] = adc_queue_current.analog_data[1];
app_regs.analog_data[2] = adc_queue_current.analog_data[2];
HarpCore::send_harp_reply(EVENT, APP_REG_START_ADDRESS + 7, adc_queue_current.timestamp);
}
}

// Create Harp App.
Expand Down Expand Up @@ -408,3 +490,4 @@ int main()
app.run();
}
}

92 changes: 92 additions & 0 deletions Interface/Harp.Hobgoblin/AsyncDevice.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,5 +387,97 @@ public async Task<Timestamped<AnalogDataPayload>> ReadTimestampedAnalogDataAsync
var reply = await CommandAsync(HarpCommand.ReadUInt16(AnalogData.Address), cancellationToken);
return AnalogData.GetTimestampedPayload(reply);
}

/// <summary>
/// Asynchronously reads the contents of the StartPwm register.
/// </summary>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>
/// A task that represents the asynchronous read operation. The <see cref="Task{TResult}.Result"/>
/// property contains the register payload.
/// </returns>
public async Task<StartPwmPayload> ReadStartPwmAsync(CancellationToken cancellationToken = default)
{
var reply = await CommandAsync(HarpCommand.ReadUInt32(StartPwm.Address), cancellationToken);
return StartPwm.GetPayload(reply);
}

/// <summary>
/// Asynchronously reads the timestamped contents of the StartPwm register.
/// </summary>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>
/// A task that represents the asynchronous read operation. The <see cref="Task{TResult}.Result"/>
/// property contains the timestamped register payload.
/// </returns>
public async Task<Timestamped<StartPwmPayload>> ReadTimestampedStartPwmAsync(CancellationToken cancellationToken = default)
{
var reply = await CommandAsync(HarpCommand.ReadUInt32(StartPwm.Address), cancellationToken);
return StartPwm.GetTimestampedPayload(reply);
}

/// <summary>
/// Asynchronously writes a value to the StartPwm register.
/// </summary>
/// <param name="value">The value to be stored in the register.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>The task object representing the asynchronous write operation.</returns>
public async Task WriteStartPwmAsync(StartPwmPayload value, CancellationToken cancellationToken = default)
{
var request = StartPwm.FromPayload(MessageType.Write, value);
await CommandAsync(request, cancellationToken);
}

/// <summary>
/// Asynchronously reads the contents of the StopPwm register.
/// </summary>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>
/// A task that represents the asynchronous read operation. The <see cref="Task{TResult}.Result"/>
/// property contains the register payload.
/// </returns>
public async Task<byte> ReadStopPwmAsync(CancellationToken cancellationToken = default)
{
var reply = await CommandAsync(HarpCommand.ReadByte(StopPwm.Address), cancellationToken);
return StopPwm.GetPayload(reply);
}

/// <summary>
/// Asynchronously reads the timestamped contents of the StopPwm register.
/// </summary>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>
/// A task that represents the asynchronous read operation. The <see cref="Task{TResult}.Result"/>
/// property contains the timestamped register payload.
/// </returns>
public async Task<Timestamped<byte>> ReadTimestampedStopPwmAsync(CancellationToken cancellationToken = default)
{
var reply = await CommandAsync(HarpCommand.ReadByte(StopPwm.Address), cancellationToken);
return StopPwm.GetTimestampedPayload(reply);
}

/// <summary>
/// Asynchronously writes a value to the StopPwm register.
/// </summary>
/// <param name="value">The value to be stored in the register.</param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>The task object representing the asynchronous write operation.</returns>
public async Task WriteStopPwmAsync(byte value, CancellationToken cancellationToken = default)
{
var request = StopPwm.FromPayload(MessageType.Write, value);
await CommandAsync(request, cancellationToken);
}
}
}
Loading