Added thread safety to USB TX, servo control thread, primitive ADC thread, python test codes

This commit is contained in:
Your Name
2026-09-22 13:01:31 +03:00
parent 1d1416d5d8
commit dd7e6d9456
16 changed files with 944 additions and 40 deletions
+3 -1
View File
@@ -1,5 +1,6 @@
CONFIG_GPIO=y CONFIG_GPIO=y
CONFIG_PWM=y CONFIG_PWM=y
CONFIG_ADC=y
# Serial # Serial
CONFIG_SERIAL=y CONFIG_SERIAL=y
@@ -19,7 +20,8 @@ CONFIG_USBD_SELF_POWERED=y
CONFIG_USBD_MAX_POWER=125 CONFIG_USBD_MAX_POWER=125
# LOG # LOG
CONFIG_LOG=n CONFIG_LOG=y
CONFIG_CBPRINTF_FP_SUPPORT=y # For logging float values
CONFIG_USBD_CDC_ACM_LOG_LEVEL_OFF=y # This removes a pointless warning CONFIG_USBD_CDC_ACM_LOG_LEVEL_OFF=y # This removes a pointless warning
CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_LOG_MODE_IMMEDIATE=y
+5 -2
View File
@@ -12,7 +12,10 @@ COMMAND_PREFIX = 0x69
COMMAND_ACK = 0 COMMAND_ACK = 0
COMMAND_NACK = 1 COMMAND_NACK = 1
LED = 2 LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
DEVICE_ID = 0 DEVICE_ID = 0
@@ -112,7 +115,7 @@ def main():
for i in range(10): for i in range(10):
print(f"--> Sending LED command {i + 1}") print(f"--> Sending LED command {i + 1}")
ser.write(make_packet(LED)) ser.write(make_packet(LED_TOGGLE))
time.sleep(0.5) time.sleep(0.5)
print("Done.") print("Done.")
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
import serial
import struct
import threading
import time
PORT = "/dev/ttyACM0"
BAUDRATE = 115200
COMMAND_PREFIX = 0x69
COMMAND_ACK = 0
COMMAND_NACK = 1
LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
ADC_READ = 6
ADC_READ_ALL = 7
DEVICE_ID = 0
def calculate_crc(msg: bytes) -> int:
s = sum(msg) & 0xFF
return (-s) & 0xFF
def make_packet(command: int, data: bytes = b"") -> bytes:
length = len(data)
pkt = bytearray()
pkt.append(COMMAND_PREFIX)
pkt.append(length)
pkt.append(DEVICE_ID)
pkt.append(command)
pkt.append(0) # CRC placeholder
pkt.extend(data)
pkt[4] = calculate_crc(pkt[:4] + pkt[5:])
return bytes(pkt)
def verify_crc(packet: bytes) -> bool:
crc = packet[4]
calc = calculate_crc(packet[:4] + packet[5:])
return crc == calc
def packet_size(buf: bytes):
if len(buf) < 2:
return None
return 5 + buf[1]
def reader(ser):
rx = bytearray()
while True:
data = ser.read(64)
if not data:
continue
rx.extend(data)
while rx:
# Binary packet?
if rx[0] == COMMAND_PREFIX:
size = packet_size(rx)
if size is None or len(rx) < size:
break
pkt = bytes(rx[:size])
del rx[:size]
if not verify_crc(pkt):
print("RX: Bad CRC:", pkt.hex())
continue
length = pkt[1]
dev_id = pkt[2]
cmd = pkt[3]
if cmd == COMMAND_ACK:
print(f"<-- ACK (device={dev_id})")
elif cmd == COMMAND_NACK:
print(f"<-- NACK (device={dev_id})")
else:
print(f"<-- Command {cmd} len={length}")
print(f"<-- RX: {pkt.hex(' ')} ")
else:
# ASCII log output
idx = rx.find(b'\n')
if idx == -1:
break
line = rx[:idx + 1]
del rx[:idx + 1]
try:
print("[LOG]", line.decode().rstrip())
except UnicodeDecodeError:
print("[RAW]", line.hex())
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
threading.Thread(target=reader, args=(ser,), daemon=True).start()
data = bytearray()
data.append(17)
ser.write(make_packet(ADC_READ, data))
print("Done.")
time.sleep(2)
if __name__ == "__main__":
main()
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
import serial
import struct
import threading
import time
PORT = "/dev/ttyACM0"
BAUDRATE = 115200
COMMAND_PREFIX = 0x69
COMMAND_ACK = 0
COMMAND_NACK = 1
LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
ADC_READ = 6
ADC_READ_ALL = 7
DEVICE_ID = 0
def calculate_crc(msg: bytes) -> int:
s = sum(msg) & 0xFF
return (-s) & 0xFF
def make_packet(command: int, data: bytes = b"") -> bytes:
length = len(data)
pkt = bytearray()
pkt.append(COMMAND_PREFIX)
pkt.append(length)
pkt.append(DEVICE_ID)
pkt.append(command)
pkt.append(0) # CRC placeholder
pkt.extend(data)
pkt[4] = calculate_crc(pkt[:4] + pkt[5:])
return bytes(pkt)
def verify_crc(packet: bytes) -> bool:
crc = packet[4]
calc = calculate_crc(packet[:4] + packet[5:])
return crc == calc
def packet_size(buf: bytes):
if len(buf) < 2:
return None
return 5 + buf[1]
def reader(ser):
rx = bytearray()
while True:
data = ser.read(64)
if not data:
continue
rx.extend(data)
while rx:
# Binary packet?
if rx[0] == COMMAND_PREFIX:
size = packet_size(rx)
if size is None or len(rx) < size:
break
pkt = bytes(rx[:size])
del rx[:size]
if not verify_crc(pkt):
print("RX: Bad CRC:", pkt.hex())
continue
length = pkt[1]
dev_id = pkt[2]
cmd = pkt[3]
if cmd == COMMAND_ACK:
print(f"<-- ACK (device={dev_id})")
elif cmd == COMMAND_NACK:
print(f"<-- NACK (device={dev_id})")
else:
print(f"<-- Command {cmd} len={length}")
print(f"<-- RX: {pkt.hex(' ')} ")
else:
# ASCII log output
idx = rx.find(b'\n')
if idx == -1:
break
line = rx[:idx + 1]
del rx[:idx + 1]
try:
print("[LOG]", line.decode().rstrip())
except UnicodeDecodeError:
print("[RAW]", line.hex())
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
threading.Thread(target=reader, args=(ser,), daemon=True).start()
data = bytearray()
ser.write(make_packet(ADC_READ_ALL, data))
print("Done.")
time.sleep(2)
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
import serial
import struct
import threading
import time
PORT = "/dev/ttyACM0"
BAUDRATE = 115200
COMMAND_PREFIX = 0x69
COMMAND_ACK = 0
COMMAND_NACK = 1
LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
DEVICE_ID = 0
def calculate_crc(msg: bytes) -> int:
s = sum(msg) & 0xFF
return (-s) & 0xFF
def make_packet(command: int, data: bytes = b"") -> bytes:
length = len(data)
pkt = bytearray()
pkt.append(COMMAND_PREFIX)
pkt.append(length)
pkt.append(DEVICE_ID)
pkt.append(command)
pkt.append(0) # CRC placeholder
pkt.extend(data)
pkt[4] = calculate_crc(pkt[:4] + pkt[5:])
return bytes(pkt)
def verify_crc(packet: bytes) -> bool:
crc = packet[4]
calc = calculate_crc(packet[:4] + packet[5:])
return crc == calc
def packet_size(buf: bytes):
if len(buf) < 2:
return None
return 5 + buf[1]
def reader(ser):
rx = bytearray()
while True:
data = ser.read(64)
if not data:
continue
rx.extend(data)
while rx:
# Binary packet?
if rx[0] == COMMAND_PREFIX:
size = packet_size(rx)
if size is None or len(rx) < size:
break
pkt = bytes(rx[:size])
del rx[:size]
if not verify_crc(pkt):
print("RX: Bad CRC:", pkt.hex())
continue
length = pkt[1]
dev_id = pkt[2]
cmd = pkt[3]
if cmd == COMMAND_ACK:
print(f"<-- ACK (device={dev_id})")
elif cmd == COMMAND_NACK:
print(f"<-- NACK (device={dev_id})")
elif cmd == COMMAND_ERROR:
print(f"<-- ERROR (device={dev_id})")
else:
print(f"<-- Command {cmd} len={length}")
else:
# ASCII log output
idx = rx.find(b'\n')
if idx == -1:
break
line = rx[:idx + 1]
del rx[:idx + 1]
try:
print("[LOG]", line.decode().rstrip())
except UnicodeDecodeError:
print("[RAW]", line.hex())
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
threading.Thread(target=reader, args=(ser,), daemon=True).start()
angle = 0.123
data = bytearray()
for i in range(18):
data.extend(struct.pack('<f', angle))
ser.write(make_packet(SERVO_SET_ALL, data))
print("Done.")
time.sleep(2)
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
import serial
import struct
import threading
import time
PORT = "/dev/ttyACM0"
BAUDRATE = 115200
COMMAND_PREFIX = 0x69
COMMAND_ACK = 0
COMMAND_NACK = 1
LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
DEVICE_ID = 0
def calculate_crc(msg: bytes) -> int:
s = sum(msg) & 0xFF
return (-s) & 0xFF
def make_packet(command: int, data: bytes = b"") -> bytes:
length = len(data)
pkt = bytearray()
pkt.append(COMMAND_PREFIX)
pkt.append(length)
pkt.append(DEVICE_ID)
pkt.append(command)
pkt.append(0) # CRC placeholder
pkt.extend(data)
pkt[4] = calculate_crc(pkt[:4] + pkt[5:])
return bytes(pkt)
def verify_crc(packet: bytes) -> bool:
crc = packet[4]
calc = calculate_crc(packet[:4] + packet[5:])
return crc == calc
def packet_size(buf: bytes):
if len(buf) < 2:
return None
return 5 + buf[1]
def reader(ser):
rx = bytearray()
while True:
data = ser.read(64)
if not data:
continue
rx.extend(data)
while rx:
# Binary packet?
if rx[0] == COMMAND_PREFIX:
size = packet_size(rx)
if size is None or len(rx) < size:
break
pkt = bytes(rx[:size])
del rx[:size]
if not verify_crc(pkt):
print("RX: Bad CRC:", pkt.hex())
continue
length = pkt[1]
dev_id = pkt[2]
cmd = pkt[3]
if cmd == COMMAND_ACK:
print(f"<-- ACK (device={dev_id})")
elif cmd == COMMAND_NACK:
print(f"<-- NACK (device={dev_id})")
elif cmd == COMMAND_ERROR:
print(f"<-- ERROR (device={dev_id})")
else:
print(f"<-- Command {cmd} len={length}")
else:
# ASCII log output
idx = rx.find(b'\n')
if idx == -1:
break
line = rx[:idx + 1]
del rx[:idx + 1]
try:
print("[LOG]", line.decode().rstrip())
except UnicodeDecodeError:
print("[RAW]", line.hex())
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
threading.Thread(target=reader, args=(ser,), daemon=True).start()
angle = 0.123
data = bytearray()
data.append(0)
data.extend(struct.pack('<f', angle))
ser.write(make_packet(SERVO_SET, data))
print("Done.")
time.sleep(2)
if __name__ == "__main__":
main()
+120
View File
@@ -0,0 +1,120 @@
#include "adc.h"
#include <zephyr/logging/log.h>
#include <zephyr/drivers/adc.h>
LOG_MODULE_REGISTER(adc, LOG_LEVEL_INF);
#define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
static const struct adc_dt_spec adc_mux_spec = ADC_DT_SPEC_GET_BY_IDX(ZEPHYR_USER_NODE, 0);
static const struct adc_dt_spec adc_ch16_spec = ADC_DT_SPEC_GET_BY_IDX(ZEPHYR_USER_NODE, 1);
static const struct adc_dt_spec adc_ch17_spec = ADC_DT_SPEC_GET_BY_IDX(ZEPHYR_USER_NODE, 2);
int16_t adc_buffer;
struct adc_sequence sequence = {
.buffer = &adc_buffer,
.buffer_size = sizeof(adc_buffer),
};
int adc_init_all(void) {
int ret;
// ret = mux_init();
// if (ret != 0) {
// LOG_ERR("Failed to setup multiplexer: %d", ret);
// return ret;
// }
if (!adc_is_ready_dt(&adc_mux_spec)) {
LOG_ERR("ADC mux channel not ready");
return -1;
}
if (!adc_is_ready_dt(&adc_ch16_spec)) {
LOG_ERR("ADC channel 17 not ready");
return -1;
}
if (!adc_is_ready_dt(&adc_ch17_spec)) {
LOG_ERR("ADC channel 18 not ready");
return -1;
}
ret = adc_channel_setup_dt(&adc_mux_spec);
if (ret != 0) {
LOG_ERR("Failed to setup ADC mux: %d", ret);
return ret;
}
ret = adc_channel_setup_dt(&adc_ch16_spec);
if (ret != 0) {
LOG_ERR("Failed to setup ADC channel 17: %d", ret);
return ret;
}
ret = adc_channel_setup_dt(&adc_ch17_spec);
if (ret != 0) {
LOG_ERR("Failed to setup ADC channel 18: %d", ret);
return ret;
}
adc_sequence_init_dt(&adc_mux_spec, &sequence);
adc_sequence_init_dt(&adc_ch16_spec, &sequence);
adc_sequence_init_dt(&adc_ch17_spec, &sequence);
return 0;
}
int adc_read_id(int id) {
int ret;
int val_mv = 0;
adc_buffer = 0;
if (id < MUX_CHANNELS) {
// Read muxed channels
(void)adc_sequence_init_dt(&adc_mux_spec, &sequence);
// Set mux TODO:
// ret = mux_select_channel(id);
// if (ret < 0) {
// LOG_ERR("Could not set multiplexer (%d)\n", ret);
// return 0;
// }
ret = adc_read_dt(&adc_mux_spec, &sequence);
if (ret < 0) {
LOG_ERR("Could not read (%d)\n", ret);
return 0;
}
val_mv = (int)adc_buffer;
// ret = adc_raw_to_millivolts_dt(&adc_mux_spec, &val_mv);
}
else if (id == MUX_CHANNELS) {
// Read unmuxed channel 17
(void)adc_sequence_init_dt(&adc_ch16_spec, &sequence);
ret = adc_read_dt(&adc_ch16_spec, &sequence);
if (ret < 0) {
LOG_ERR("Could not read (%d)\n", ret);
return 0;
}
val_mv = (int)adc_buffer;
// ret = adc_raw_to_millivolts_dt(&adc_ch16_spec, &val_mv);
}
else if (id == (MUX_CHANNELS + 1)) {
// Read unmuxed channel 18
(void)adc_sequence_init_dt(&adc_ch17_spec, &sequence);
ret = adc_read_dt(&adc_ch17_spec, &sequence);
if (ret < 0) {
LOG_ERR("Could not read (%d)\n", ret);
return 0;
}
val_mv = (int)adc_buffer;
// ret = adc_raw_to_millivolts_dt(&adc_ch17_spec, &val_mv);
}
return val_mv;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef ADC_H
#define ADC_H
#define MUX_CHANNELS 16
#define NUM_ADC_CHANNELS 18
int adc_init_all();
int adc_read_id(int id);
#endif // ADC_H
+51
View File
@@ -1,5 +1,9 @@
#include "command_handler.h" #include "command_handler.h"
#include "command_message.h"
#include "led.h" #include "led.h"
#include "servo.h"
#include "adc.h"
#include "usb.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
@@ -37,6 +41,53 @@ int command_handler(struct command_message_t *msg) {
break; break;
} }
case SERVO_SET: {
// Set SERVO
int servo = msg->data[0];
float angle;
memcpy(&angle, &msg->data[1], sizeof(float));
servo_set_angle(servo, angle);
break;
}
case SERVO_SET_ALL: {
// Set ALL SERVO
float angles[NUM_SERVO_CHANNELS];
for (int i = 0; i < NUM_SERVO_CHANNELS; i++) {
int start = i*4;
memcpy(&angles[i], &msg->data[start], sizeof(float));
}
servo_set_all_angles(angles);
break;
}
case ADC_READ: {
int channel = msg->data[0];
int value = adc_read_id(channel);
struct command_message_t *reply = usb_get_next_tx_buf();
command_create_message(reply, sizeof(value), ADC_READ, (uint8_t *)&value);
usb_send_command(reply);
break;
}
case ADC_READ_ALL: {
int values[NUM_ADC_CHANNELS];
for (int i = 0; i < NUM_ADC_CHANNELS; i++) {
values[i] = adc_read_id(i);
}
struct command_message_t *reply = usb_get_next_tx_buf();
command_create_message(reply, sizeof(values), ADC_READ_ALL, (uint8_t *)values);
usb_send_command(reply);
break;
}
default: { default: {
LOG_WRN("Unknown command received: %d", msg->command); LOG_WRN("Unknown command received: %d", msg->command);
return -EINVAL; return -EINVAL;
+1 -1
View File
@@ -11,7 +11,7 @@ void command_message_init(struct command_message_t *msg) {
msg->id = COMMAND_ID; msg->id = COMMAND_ID;
} }
void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[160]) { void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[]) {
// Ensure length doesn't exceed available space // Ensure length doesn't exceed available space
if (length > sizeof(msg->data) - 1) { if (length > sizeof(msg->data) - 1) {
return; return;
+9 -1
View File
@@ -8,12 +8,20 @@
#define COMMAND_PREFIX 0x69 #define COMMAND_PREFIX 0x69
#define COMMAND_ID 0x00 #define COMMAND_ID 0x00
#define COMMAND_DATA_SIZE 160 #define COMMAND_DATA_SIZE 160
#define COMMAND_HEADER_SIZE 5
typedef enum { typedef enum {
COMMAND_ACK, COMMAND_ACK,
COMMAND_NACK, COMMAND_NACK,
LED_TOGGLE, LED_TOGGLE,
LED_SET, LED_SET,
SERVO_SET,
SERVO_SET_ALL,
ADC_READ,
ADC_READ_ALL,
// Keep last
NUM_COMMANDS,
} commands_e; } commands_e;
struct command_message_t { struct command_message_t {
@@ -40,7 +48,7 @@ void command_message_init(struct command_message_t *msg);
* @param command Command type * @param command Command type
* @param data Data payload * @param data Data payload
*/ */
void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[160]); void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[]);
/** /**
* @brief Calculate CRC for command message * @brief Calculate CRC for command message
+18
View File
@@ -1,5 +1,7 @@
#include "led.h" #include "led.h"
#include "usb.h" #include "usb.h"
#include "servo.h"
#include "adc.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
@@ -8,17 +10,33 @@ LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
int main(void) { int main(void) {
int ret; int ret;
// USB init
ret = usb_init(); ret = usb_init();
if (ret != 0) { if (ret != 0) {
LOG_ERR("Failed to enable USB"); LOG_ERR("Failed to enable USB");
return 0; return 0;
} }
// LED init
ret = led_init(); ret = led_init();
if (ret != 0) { if (ret != 0) {
LOG_ERR("Failed to enable LED"); LOG_ERR("Failed to enable LED");
return 0; return 0;
} }
// ADC init before SERVO
ret = adc_init_all();
if (ret != 0) {
LOG_ERR("Failed to enable ADC");
return 0;
}
// SERVO init
ret = servo_init();
if (ret != 0) {
LOG_ERR("Failed to enable SERVO");
return 0;
}
return 0; return 0;
} }
+113 -2
View File
@@ -2,10 +2,14 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <zephyr/drivers/pwm.h> #include <zephyr/drivers/pwm.h>
#include <zephyr/kernel.h>
#include <math.h>
LOG_MODULE_REGISTER(servo, LOG_LEVEL_INF); LOG_MODULE_REGISTER(servo, LOG_LEVEL_INF);
// ----------- SERVOS -----------
// SERVOS
static const struct pwm_dt_spec servo_pwm_specs[NUM_SERVO_CHANNELS] = { static const struct pwm_dt_spec servo_pwm_specs[NUM_SERVO_CHANNELS] = {
PWM_DT_SPEC_GET(DT_ALIAS(servo1)), PWM_DT_SPEC_GET(DT_ALIAS(servo1)),
PWM_DT_SPEC_GET(DT_ALIAS(servo2)), PWM_DT_SPEC_GET(DT_ALIAS(servo2)),
@@ -26,12 +30,119 @@ static const struct pwm_dt_spec servo_pwm_specs[NUM_SERVO_CHANNELS] = {
PWM_DT_SPEC_GET(DT_ALIAS(servo17)), PWM_DT_SPEC_GET(DT_ALIAS(servo17)),
PWM_DT_SPEC_GET(DT_ALIAS(servo18)), PWM_DT_SPEC_GET(DT_ALIAS(servo18)),
}; };
static struct servo_t servos[NUM_SERVO_CHANNELS];
// THREAD
static struct k_thread servo_thread_data;
static k_tid_t servo_thread_id = NULL;
#define SERVO_THREAD_STACK_SIZE 2048
K_THREAD_STACK_DEFINE(servo_thread_stack, SERVO_THREAD_STACK_SIZE);
struct k_sem servo_semaphore;
static int pin_init(const ) { static void servo_thread(void *p1, void *p2, void *p3) {
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
LOG_INF("Servo control thread started");
while (1) {
k_sem_take(&servo_semaphore, K_FOREVER);
// TODO: acceleration?
for (int i = 0; i < NUM_SERVO_CHANNELS; i++) {
servo_set(i, servos[i].set_position);
servos[i].current_position = servos[i].set_position;
}
}
} }
int servo_init() { int servo_init() {
int ret;
k_sem_init(&servo_semaphore, 0, 1);
memset(servos, 0, sizeof(servos));
for (int ch = 0; ch < NUM_SERVO_CHANNELS; ch++) {
if (!pwm_is_ready_dt(&servo_pwm_specs[ch])) {
LOG_ERR("PWM device for servo %d is not ready", ch + 1);
return -ENODEV;
}
}
for (int ch = 0; ch < NUM_SERVO_CHANNELS; ch++) {
ret = pwm_set_pulse_dt(&servo_pwm_specs[ch], PWM_USEC(0));
if (ret != 0) {
LOG_ERR("Failed to initialize PWM for servo %d: %d", ch + 1, ret);
return ret;
}
}
servo_thread_id = k_thread_create(
&servo_thread_data,
servo_thread_stack,
K_THREAD_STACK_SIZEOF(servo_thread_stack),
servo_thread,
NULL, NULL, NULL,
5,
0,
K_NO_WAIT
);
return 0;
}
void servo_set_angle(int channel, float angle) {
servos[channel].set_position = angle;
LOG_DBG("Settings servo %d to %f", channel, (double)angle);
k_sem_give(&servo_semaphore);
}
void servo_set_all_angles(float angles[NUM_SERVO_CHANNELS]) {
for (int i = 0; i < NUM_SERVO_CHANNELS; i++) {
servos[i].set_position = angles[i];
LOG_DBG("Settings servo %d to %f", i, (double)angles[i]);
}
k_sem_give(&servo_semaphore);
}
static uint32_t servo_angle_to_pulse_us(float angle) {
// Check limits
if (angle > SERVO_MAX_ANGLE) {
return SERVO_MAX_PULSE;
} else if (angle < SERVO_MIN_ANGLE) {
return SERVO_MIN_PULSE;
}
// Angle to pulse
float pulse_offset_us = angle * SERVO_ANGLE_PULSE_MULTIPLIER;
uint32_t pulse_us = (uint32_t)(SERVO_CENTER_PULSE + pulse_offset_us);
if (pulse_us < SERVO_MIN_PULSE) {
return SERVO_MIN_PULSE;
} else if (pulse_us > SERVO_MAX_PULSE) {
return SERVO_MAX_PULSE;
}
return pulse_us;
}
int servo_set(int channel, float angle) {
if (channel >= NUM_SERVO_CHANNELS || channel < 0) {
LOG_ERR("Servo channel exceeds NUM_SERVO_CHANNELS");
return -EINVAL;
}
float effective_angle = angle;
uint32_t pulse_us = servo_angle_to_pulse_us(effective_angle);
int ret = pwm_set_pulse_dt(&servo_pwm_specs[channel], PWM_USEC(pulse_us));
if (ret != 0) {
LOG_ERR("Failed to set PWM pulse for servo %d: %d", channel + 1, ret);
return ret;
}
return 0;
} }
+15 -1
View File
@@ -2,12 +2,26 @@
#define SERVO_H #define SERVO_H
#define NUM_SERVO_CHANNELS 18 #define NUM_SERVO_CHANNELS 18
#define SERVO_CENTER_PULSE 1500
#define SERVO_MIN_PULSE 750
#define SERVO_MAX_PULSE 2500
#define SERVO_HALF_PULSE_RANGE 875
#define SERVO_MAX_ANGLE 2.3561944902f
#define SERVO_MIN_ANGLE -2.3561944902f
#define SERVO_ANGLE_PULSE_MULTIPLIER 371.3615338798826f
struct servo_t {
float current_position;
float set_position;
};
int servo_init(); int servo_init();
void servo_set_angle(int channel, float angle);
void servo_set_all_angles(float angles[NUM_SERVO_CHANNELS]);
int servo_set(int channel, float angle);
#endif // SERVO_H #endif // SERVO_H
+87 -31
View File
@@ -17,11 +17,11 @@ LOG_MODULE_REGISTER(usb, LOG_LEVEL_INF);
const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
static struct usbd_context *usb_context; static struct usbd_context *usb_context;
// THREAD // RX THREAD
static struct k_thread usb_thread_data; static struct k_thread usb_rx_thread_data;
static k_tid_t usb_thread_id = NULL; static k_tid_t usb_rx_thread_id = NULL;
#define USB_THREAD_STACK_SIZE 2048 #define USB_RX_THREAD_STACK_SIZE 2048
K_THREAD_STACK_DEFINE(usb_thread_stack, USB_THREAD_STACK_SIZE); K_THREAD_STACK_DEFINE(usb_rx_thread_stack, USB_RX_THREAD_STACK_SIZE);
// RX BUFFER // RX BUFFER
#define RING_BUF_SIZE 255 #define RING_BUF_SIZE 255
@@ -29,10 +29,21 @@ static uint8_t ring_buffer[RING_BUF_SIZE];
static struct ring_buf ringbuf; static struct ring_buf ringbuf;
struct k_sem rx_semaphore; struct k_sem rx_semaphore;
// TX THREAD
static struct k_thread usb_tx_thread_data;
static k_tid_t usb_tx_thread_id = NULL;
#define USB_TX_THREAD_STACK_SIZE 512
K_THREAD_STACK_DEFINE(usb_tx_thread_stack, USB_TX_THREAD_STACK_SIZE);
// TX BUFFER (add ack and nack at the and as static)
#define TX_BUFFER_SIZE 10
struct command_message_t usb_tx_buffer[TX_BUFFER_SIZE + 2];
struct command_message_t *usb_tx_buf_ptr;
char usb_tx_ptr_msgq_buffer[(TX_BUFFER_SIZE + 2) * sizeof(struct command_message_t *)];
struct k_msgq usb_tx_ptr_msgq;
// ACK / NACK messages // ACK / NACK messages
#define RETURN_ACK true #define RETURN_ACK true
struct command_message_t ack_msg;
struct command_message_t nack_msg;
static void interrupt_handler(const struct device *dev, void *user_data) { static void interrupt_handler(const struct device *dev, void *user_data) {
@@ -73,7 +84,7 @@ static void interrupt_handler(const struct device *dev, void *user_data) {
} }
} }
static void usb_thread(void *p1, void *p2, void *p3) { static void usb_rx_thread(void *p1, void *p2, void *p3) {
ARG_UNUSED(p1); ARG_UNUSED(p1);
ARG_UNUSED(p2); ARG_UNUSED(p2);
ARG_UNUSED(p3); ARG_UNUSED(p3);
@@ -109,7 +120,7 @@ static void usb_thread(void *p1, void *p2, void *p3) {
if (calculated_crc != msg.crc) { if (calculated_crc != msg.crc) {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send NACK // Send NACK
usb_send_command(&nack_msg); usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE + 1]);
} }
continue; continue;
} }
@@ -118,13 +129,13 @@ static void usb_thread(void *p1, void *p2, void *p3) {
if (ret == 0) { if (ret == 0) {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send ACK // Send ACK
usb_send_command(&ack_msg); usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE]);
} }
} }
else { else {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send NACK // Send NACK
usb_send_command(&nack_msg); usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE + 1]);
} }
} }
@@ -162,12 +173,36 @@ static void usb_msg_cb(struct usbd_context *const ctx, const struct usbd_msg *ms
} }
} }
static void usb_tx_thread(void *p1, void *p2, void *p3) {
struct command_message_t *data;
while (1) {
k_msgq_get(&usb_tx_ptr_msgq, &data, K_FOREVER);
if (!device_is_ready(uart_dev)) {
// FIXME: Don't drop packages
continue;
}
// Message size: prefix + length + id + command + crc + data
size_t msg_size = COMMAND_HEADER_SIZE + data->length;
uint8_t *msg_bytes = (uint8_t *)data;
/* uart_poll_out blocks until sent, ensuring data integrity */
for (size_t i = 0; i < msg_size; i++) {
uart_poll_out(uart_dev, msg_bytes[i]);
}
}
}
int usb_init() { int usb_init() {
ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer); ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer);
k_sem_init(&rx_semaphore, 0, 1); k_sem_init(&rx_semaphore, 0, 1);
usb_tx_buf_ptr = usb_tx_buffer;
k_msgq_init(&usb_tx_ptr_msgq, usb_tx_ptr_msgq_buffer, sizeof(struct command_message_t *), TX_BUFFER_SIZE);
command_create_ack(&ack_msg); command_create_ack(&usb_tx_buffer[TX_BUFFER_SIZE]);
command_create_nack(&nack_msg); command_create_nack(&usb_tx_buffer[TX_BUFFER_SIZE + 1]);
int ret; int ret;
@@ -194,38 +229,59 @@ int usb_init() {
uart_irq_callback_set(uart_dev, interrupt_handler); uart_irq_callback_set(uart_dev, interrupt_handler);
uart_irq_rx_enable(uart_dev); uart_irq_rx_enable(uart_dev);
usb_thread_id = k_thread_create( // RX THREAD
&usb_thread_data, usb_rx_thread_id = k_thread_create(
usb_thread_stack, &usb_rx_thread_data,
K_THREAD_STACK_SIZEOF(usb_thread_stack), usb_rx_thread_stack,
usb_thread, K_THREAD_STACK_SIZEOF(usb_rx_thread_stack),
usb_rx_thread,
NULL, NULL, NULL, NULL, NULL, NULL,
5, 5,
0, 0,
K_NO_WAIT K_NO_WAIT
); );
if (usb_thread_id == NULL) { if (usb_rx_thread_id == NULL) {
LOG_ERR("Failed to create USB thread"); LOG_ERR("Failed to create USB RX thread");
return -ENOMEM;
}
// TX THREAD
usb_tx_thread_id = k_thread_create(
&usb_tx_thread_data,
usb_tx_thread_stack,
K_THREAD_STACK_SIZEOF(usb_tx_thread_stack),
usb_tx_thread,
NULL, NULL, NULL,
5,
0,
K_NO_WAIT
);
if (usb_tx_thread_id == NULL) {
LOG_ERR("Failed to create USB TX thread");
return -ENOMEM; return -ENOMEM;
} }
return ret; return ret;
} }
struct command_message_t* usb_get_next_tx_buf() {
struct command_message_t *buf = usb_tx_buf_ptr;
// Increment the buffer pointer
usb_tx_buf_ptr++;
if (usb_tx_buf_ptr > &usb_tx_buffer[TX_BUFFER_SIZE-1]) {
usb_tx_buf_ptr = usb_tx_buffer;
}
return buf;
}
int usb_send_command(struct command_message_t *msg) { int usb_send_command(struct command_message_t *msg) {
if (!device_is_ready(uart_dev)) {
return -ENODEV;
}
// Message size: prefix + length + id + command + crc + data k_msgq_put(&usb_tx_ptr_msgq, &msg, K_NO_WAIT);
size_t msg_size = 5 + msg->length;
uint8_t *msg_bytes = (uint8_t *)msg;
/* uart_poll_out blocks until sent, ensuring data integrity */
for (size_t i = 0; i < msg_size; i++) {
uart_poll_out(uart_dev, msg_bytes[i]);
}
return 0; return 0;
} }
+1
View File
@@ -6,6 +6,7 @@
int usb_init(); int usb_init();
struct command_message_t* usb_get_next_tx_buf();
int usb_send_command(struct command_message_t *msg); int usb_send_command(struct command_message_t *msg);