Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
|
- I wanted to make a Ultra-compact plastic BBs launcher that could shoot BBs, so after a lot of trial and error, I ended up with a pitching machine design.
- I went with a screw-type BBs feeder.
- I'm using an M5Stack StampS3 microcontroller to control the three motors.
- Also, I can control it remotely from an AtomJoystick using ESP-NOW
- I created a crawler with a modified toio and mounted a launcher on top of it.
- The toio can determine its position and orientation with high precision by optically reading the dots printed on the mat. By attaching the mat to the bottom of the launcher and mounting a toio upside down, the firing direction can be specified accurately. This makes it possible to automatically launch attacks aimed at the coordinates of the target.
- For filming purposes, the distance between the launcher and the target is kept short, but it is accurate enough to hit targets over one meter away.
For more information about toio, please visit the following website.
https://toio.github.io/toio-spec/en/docs/about/
1 / 9
/*
* MIT License
*
* Copyright (c) 2024 Kouhei Ito
* Copyright (c) 2025 Jun Kataoka
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Arduino.h>
#include <FastLED.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <FastLED.h>
void telemetry(void);
// グローバル関数の宣言
void init_copter(void);
void loop_400Hz(void);
//Mode
#define INIT_MODE 0
#define AVERAGE_MODE 1
#define FLIGHT_MODE 2
#define PARKING_MODE 3
typedef struct{
volatile uint8_t mode=0;
volatile uint8_t oldmode=0;
volatile uint8_t loop=0;
}flag_t;
typedef struct{
uint16_t loop=0;
uint16_t offset=0;
uint32_t counter=0;
}counter_t;
typedef struct{
float elapsed_time;
float old_elapsed_time;
float interval_time;
uint32_t start_time;
}times_t;
typedef struct{
flag_t flag;
counter_t counter;
times_t times;
}stampfly_t;
extern stampfly_t StampFly;
// #define MINIJOYC
#define CHANNEL (3)
#define JOY (0)
#define TELEM (0)
#define RUDDER (0)
#define ELEVATOR (1)
#define THROTTLE (2)
#define AILERON (3)
#define LOG (4)
#define DPAD_UP (5)
#define DPAD_DOWN (6)
#define DPAD_LEFT (7)
#define DPAD_RIGHT (8)
#define BUTTON_ARM (9)
#define BUTTON_FLIP (10)
#define CONTROLMODE (11)
#define ALTCONTROLMODE (12)
void rc_init(void);
void rc_demo(void);
void rc_end(void);
uint8_t rc_isconnected(void);
uint8_t telemetry_send(esp_now_peer_info_t* peerInfo, uint8_t *data, uint16_t datalen);
void send_peer_info(void);
extern volatile float Stick[16];
extern volatile uint8_t Rc_err_flag;
extern volatile uint8_t MyMacAddr[6];
extern volatile uint8_t Recv_MAC[3];
extern volatile uint16_t Connect_flag;
extern esp_now_peer_info_t peerInfo[2];
uint8_t Telem_mode = 0;
uint8_t Telem_cnt = 0;
const uint8_t MAXINDEX = 11*4+2;
const uint8_t MININDEX = 30;
void telemetry_sequence(void);
void telemetry_sequence_fast(void);
void make_telemetry_header_data(uint8_t* senddata);
void make_telemetry_data(uint8_t* senddata);
void make_telemetry_data_fast(uint8_t* senddata);
void data2log(uint8_t* data_list, float add_data, uint8_t index);
void float2byte(float x, uint8_t* dst);
void append_data(uint8_t* data, uint8_t* newdata, uint8_t index, uint8_t len);
void data_set(uint8_t* datalist, float value, uint8_t* index);
void data_set_uint32(uint8_t* datalist, uint32_t value, uint8_t* index);
void data_set_uint16(uint8_t* datalist, uint16_t value, uint8_t* index);
void data_set_uint8(uint8_t* datalist, uint8_t value, uint8_t* index);
stampfly_t StampFly;
void IRAM_ATTR onTimer(void);
void init_copter(void);
void update_loop400Hz(void);
void init_mode(void);
void average_mode(void);
void flight_mode(void);
void parking_mode(void);
void loop_400Hz(void);
volatile uint16_t Connect_flag = 0;
// Telemetry相手のMAC ADDRESS 4C:75:25:AD:B6:6C
// ATOM Lite (C): 4C:75:25:AE:27:FC
// 4C:75:25:AD:8B:20
// 4C:75:25:AF:4E:84
// 4C:75:25:AD:8B:20
// 4C:75:25:AD:8B:20 赤水玉テープ ATOM lite
uint8_t JoyAddr[6] = {0};
uint8_t TelemAddr[6] = {0x4C, 0x75, 0x25, 0xAD, 0x8B, 0x20};
volatile uint8_t MyMacAddr[6];
volatile uint8_t peer_command[4] = {0xaa, 0x55, 0x16, 0x88};
volatile uint8_t Rc_err_flag = 0;
esp_now_peer_info_t peerInfo[2];
esp_now_send_status_t esp_now_send_status;
volatile uint8_t SendAddress[6];
// RC
volatile float Stick[16];
volatile uint8_t Recv_MAC[3];
void on_esp_now_sent(const uint8_t *mac_addr, esp_now_send_status_t status);
void telemetry(void) {
//uint8_t senddata[MAXINDEX];
if (Telem_mode == 0) {
// Send header data
Telem_mode = 1;
//make_telemetry_header_data(senddata);
// Send !
//telemetry_send(senddata, sizeof(senddata));
} else if (StampFly.flag.mode > AVERAGE_MODE) {
const uint8_t N = 10;
// N回に一度送信
if (Telem_cnt == 0) telemetry_sequence();
Telem_cnt++;
if (Telem_cnt > N - 1) Telem_cnt = 0;
// telemetry_sequence();
}
}
void telemetry_sequence(void) {
uint8_t senddata[MAXINDEX];
switch (Telem_mode) {
case 1:
make_telemetry_data(senddata);
// Send !
if (telemetry_send(&peerInfo[TELEM], senddata, sizeof(senddata)) == 1){
//USBSerial.printf("NG Mode=%d\n\r", StampFly.flag.mode);
}
else{
//USBSerial.printf("OK Mode=%d\n\r", StampFly.flag.mode);
}
// Telem_mode = 2;
break;
}
}
void make_telemetry_header_data(uint8_t* senddata) {
float d_float;
uint8_t d_int[4];
uint8_t index = 0;
index = 2;
for (uint8_t i = 0; i < (MAXINDEX - 2) / 4; i++) {
data2log(senddata, 0.0f, index);
index = index + 4;
}
// Telemetry Header
senddata[0] = 99;
senddata[1] = 99;
index = 2;
#if 0
data_set(senddata, Roll_rate_kp, &index);
data_set(senddata, Roll_rate_ti, &index);
data_set(senddata, Roll_rate_td, &index);
data_set(senddata, Roll_rate_eta, &index);
data_set(senddata, Pitch_rate_kp, &index);
data_set(senddata, Pitch_rate_ti, &index);
data_set(senddata, Pitch_rate_td, &index);
data_set(senddata, Pitch_rate_eta, &index);
data_set(senddata, Yaw_rate_kp, &index);
data_set(senddata, Yaw_rate_ti, &index);
data_set(senddata, Yaw_rate_td, &index);
data_set(senddata, Yaw_rate_eta, &index);
data_set(senddata, Rall_angle_kp, &index);
data_set(senddata, Rall_angle_ti, &index);
data_set(senddata, Rall_angle_td, &index);
data_set(senddata, Rall_angle_eta, &index);
data_set(senddata, Pitch_angle_kp, &index);
data_set(senddata, Pitch_angle_ti, &index);
data_set(senddata, Pitch_angle_td, &index);
data_set(senddata, Pitch_angle_eta, &index);
#endif
}
void make_telemetry_data(uint8_t* senddata) {
float d_float;
uint8_t d_int[4];
uint8_t index = 0;
// Telemetry Header
senddata[0] = 88;
senddata[1] = 88;
index = 2;
// data_set(senddata, StampFly.times.elapsed_time, &index); // 1 Time
// data_set(senddata, StampFly.times.interval_time, &index); // 2 Interval Time
}
void data_set(uint8_t* datalist, float value, uint8_t* index) {
data2log(datalist, value, *index);
*index = *index + 4;
}
void data_set_uint32(uint8_t* datalist, uint32_t value, uint8_t* index) {
append_data(datalist, (uint8_t*)&value, *index, 4);
*index = *index + 4;
}
void data_set_uint16(uint8_t* datalist, uint16_t value, uint8_t* index) {
append_data(datalist, (uint8_t*)&value, *index, 2);
*index = *index + 2;
}
void data_set_uint8(uint8_t* datalist, uint8_t value, uint8_t* index) {
append_data(datalist, (uint8_t*)&value, *index, 1);
*index = *index + 1;
}
void data2log(uint8_t* data_list, float add_data, uint8_t index) {
uint8_t d_int[4];
float d_float = add_data;
float2byte(d_float, d_int);
append_data(data_list, d_int, index, 4);
}
void float2byte(float x, uint8_t* dst) {
uint8_t* dummy;
dummy = (uint8_t*)&x;
dst[0] = dummy[0];
dst[1] = dummy[1];
dst[2] = dummy[2];
dst[3] = dummy[3];
}
void append_data(uint8_t* data, uint8_t* newdata, uint8_t index, uint8_t len) {
for (uint8_t i = index; i < index + len; i++) {
data[i] = newdata[i - index];
}
}
// 受信コールバック
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *recv_data, int data_len) {
Connect_flag = 0;
uint8_t *d_int;
// int16_t d_short;
float d_float;
if (!JoyAddr[0] && !JoyAddr[1] && !JoyAddr[2] && !JoyAddr[3] && !JoyAddr[4] && !JoyAddr[5]) {
memcpy(JoyAddr, mac_addr, 6);
memcpy((void*)peerInfo[JOY].peer_addr, JoyAddr, 6);
peerInfo[JOY].channel = CHANNEL;
peerInfo[JOY].encrypt = false;
if (esp_now_add_peer(&peerInfo[JOY]) != ESP_OK) {
USBSerial.println("Failed to add peer2");
memset(JoyAddr, 0, 6);
} else {
esp_now_register_send_cb(on_esp_now_sent);
}
}
Recv_MAC[0] = recv_data[0];
Recv_MAC[1] = recv_data[1];
Recv_MAC[2] = recv_data[2];
if ((recv_data[0] == MyMacAddr[3]) && (recv_data[1] == MyMacAddr[4]) && (recv_data[2] == MyMacAddr[5])) {
Rc_err_flag = 0;
} else {
Rc_err_flag = 1;
return;
}
// checksum
uint8_t check_sum = 0;
for (uint8_t i = 0; i < 24; i++) check_sum = check_sum + recv_data[i];
// if (check_sum!=recv_data[23])USBSerial.printf("checksum=%03d recv_sum=%03d\n\r", check_sum, recv_data[23]);
if (check_sum != recv_data[24]) {
Rc_err_flag = 1;
return;
}
d_int = (uint8_t *)&d_float;
d_int[0] = recv_data[3];
d_int[1] = recv_data[4];
d_int[2] = recv_data[5];
d_int[3] = recv_data[6];
Stick[RUDDER] = d_float;
d_int[0] = recv_data[7];
d_int[1] = recv_data[8];
d_int[2] = recv_data[9];
d_int[3] = recv_data[10];
Stick[THROTTLE] = d_float;
d_int[0] = recv_data[11];
d_int[1] = recv_data[12];
d_int[2] = recv_data[13];
d_int[3] = recv_data[14];
Stick[AILERON] = d_float;
d_int[0] = recv_data[15];
d_int[1] = recv_data[16];
d_int[2] = recv_data[17];
d_int[3] = recv_data[18];
Stick[ELEVATOR] = d_float;
Stick[BUTTON_ARM] = recv_data[19];//auto_up_down_status
Stick[BUTTON_FLIP] = recv_data[20];
Stick[CONTROLMODE] = recv_data[21];//Mode:rate or angle control
Stick[ALTCONTROLMODE] = recv_data[22];//高度制御
//****************************************** */
// Serial2.printf("%f \n",Stick[ELEVATOR]);
ledcWrite(0, (uint32_t)(127 * Stick[CONTROLMODE]));
ledcWrite(1, (uint32_t)(255 * Stick[ALTCONTROLMODE]));
//****************************************** */
uint8_t ahrs_reset_flag = recv_data[23];
Stick[LOG] = 0.0;
// if (check_sum!=recv_data[23])USBSerial.printf("checksum=%03d recv_sum=%03d\n\r", check_sum, recv_data[23]);
}
// 送信コールバック
void on_esp_now_sent(const uint8_t *mac_addr, esp_now_send_status_t status) {
esp_now_send_status = status;
memcpy((void*)SendAddress, mac_addr, 6);
}
void rc_init(void) {
// Initialize Stick list
for (uint8_t i = 0; i < 16; i++) Stick[i] = 0.0;
// ESP-NOW初期化
WiFi.mode(WIFI_STA);
WiFi.disconnect();
WiFi.macAddress((uint8_t *)MyMacAddr);
USBSerial.printf("MAC ADDRESS: %02X:%02X:%02X:%02X:%02X:%02X\r\n", MyMacAddr[0], MyMacAddr[1], MyMacAddr[2],
MyMacAddr[3], MyMacAddr[4], MyMacAddr[5]);
if (esp_now_init() == ESP_OK) {
USBSerial.println("ESPNow Init Success");
} else {
USBSerial.println("ESPNow Init Failed");
ESP.restart();
}
// MACアドレスブロードキャスト
uint8_t addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
memcpy((void*)peerInfo[JOY].peer_addr, addr, 6);
peerInfo[JOY].channel = CHANNEL;
peerInfo[JOY].encrypt = false;
if (esp_now_add_peer(&peerInfo[JOY]) != ESP_OK) {
USBSerial.println("Failed to add peer");
return;
}
esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE);
// Send my MAC address
for (uint16_t i = 0; i < 50; i++) {
send_peer_info();
delay(50);
//USBSerial.printf("%d\n", i);
}
// ESP-NOW再初期化
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
USBSerial.println("ESPNow Init Success2");
} else {
USBSerial.println("ESPNow Init Failed2");
ESP.restart();
}
//テレメトリーM5ATOMとペアリング
memcpy((void*)peerInfo[TELEM].peer_addr, TelemAddr, 6);
peerInfo[TELEM].channel = CHANNEL;
peerInfo[TELEM].encrypt = false;
if (esp_now_add_peer(&peerInfo[TELEM]) != ESP_OK)
{
USBSerial.println("Failed to telemetry add peer2");
}
else USBSerial.printf("Telemetry peering Sucess!\n\r");
esp_now_register_send_cb(on_esp_now_sent);
// ESP-NOWコールバック登録
esp_now_register_recv_cb(OnDataRecv);
USBSerial.println("ESP-NOW Ready.");
}
void send_peer_info(void) {
uint8_t data[11];
data[0] = CHANNEL;
memcpy(&data[1], (uint8_t *)MyMacAddr, 6);
memcpy(&data[1 + 6], (uint8_t *)peer_command, 4);
esp_now_send(peerInfo[JOY].peer_addr, data, 11);
}
uint8_t telemetry_send(esp_now_peer_info_t* peerInfo, uint8_t *data, uint16_t datalen) {
static uint32_t cnt = 0;
static uint8_t error_flag = 0;
static uint8_t state = 0;
esp_err_t result;
if ((error_flag == 0) && (state == 0)) {
result = esp_now_send(peerInfo->peer_addr, data, datalen);
cnt = 0;
//USBSerial.printf("%d\n\r", result);
} else
cnt++;
if (esp_now_send_status == ESP_NOW_SEND_SUCCESS) {
error_flag = 0;
// state = 0;
} else {
error_flag = 1;
// state = 1;
}
// 一度送信エラーを検知してもしばらくしたら復帰する
if (cnt > 500) {
error_flag = 0;
cnt = 0;
}
cnt++;
//USBSerial.printf("%6d %d %d\r\n", cnt, error_flag, esp_now_send_status);
return error_flag;
}
void rc_end(void) {
// Ps3.end();
}
uint8_t rc_isconnected(void) {
bool status;
Connect_flag++;
if (Connect_flag < 40)
status = 1;
else
status = 0;
// USBSerial.printf("%d \n\r", Connect_flag);
return status;
}
void rc_demo() {
}
// Main loop
void loop_400Hz(void) {
// 400Hzで以降のコードが実行
update_loop400Hz();
// Mode select
if (StampFly.flag.mode == INIT_MODE)
init_mode();
else if (StampFly.flag.mode == AVERAGE_MODE)
average_mode();
else if (StampFly.flag.mode == FLIGHT_MODE)
flight_mode();
else if (StampFly.flag.mode == PARKING_MODE)
parking_mode();
//// Telemetry
telemetry();
StampFly.flag.oldmode = StampFly.flag.mode; // Memory now mode
// End of Loop_400Hz function
}
// 割り込み関数
// Intrupt function
hw_timer_t* timer = NULL;
void IRAM_ATTR onTimer(void) {
StampFly.flag.loop = 1;
//loop_400Hz();
}
// Initialize StampFly
void init_copter(void) {
//disableCore1WDT();
// Initialize Mode
StampFly.flag.mode = INIT_MODE;
StampFly.flag.loop = 0;
// Initialize Serial communication
USBSerial.begin(115200);
// Serial2.begin(115200, SERIAL_8N1, 32, 33);
delay(1500);
USBSerial.printf("Start StampFly! Skeleton\r\n");
// motor_init();
// sensor_init();
rc_init();
// init button G0
// init_button();
// setup_pwm_buzzer();
USBSerial.printf("Finish StampFly init!\r\n");
// start_tone();
// 割り込み設定
// Initialize intrupt
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 2500, true);
timerAlarmEnable(timer);
}
//loop400Hzの更新関数
void update_loop400Hz(void) {
uint32_t now_time;
while (StampFly.flag.loop == 0);
StampFly.flag.loop = 0;
#if 0
USBSerial.printf("%9.4f %9.4f %04d\n\r",
StampFly.times.elapsed_time,
StampFly.times.interval_time,
StampFly.sensor.bottom_tof_range);
#endif
//Clock
now_time = micros();
StampFly.times.old_elapsed_time = StampFly.times.elapsed_time;
StampFly.times.elapsed_time = 1e-6 * (now_time - StampFly.times.start_time);
StampFly.times.interval_time = StampFly.times.elapsed_time - StampFly.times.old_elapsed_time;
// Read Sensor Value
// sensor_read(&StampFly.sensor);
// LED Drive
// led_drive();
}
void init_mode(void) {
// motor_stop();
StampFly.counter.offset = 0;
//Mode change
StampFly.flag.mode = AVERAGE_MODE;
return;
}
void average_mode(void) {
// Mode change
StampFly.flag.mode = PARKING_MODE;
StampFly.times.start_time = micros();
StampFly.times.old_elapsed_time = 0.0f;
return;
}
void flight_mode(void) {
//飛行するためのコードを以下に記述する
}
void parking_mode(void) {
//着陸している時に行う処理を記述する
}
void setup() {
pinMode (9, OUTPUT);
digitalWrite(9, 0);
pinMode (1, OUTPUT);
digitalWrite(1, 0);
pinMode (3, OUTPUT);
digitalWrite(3, 0);
pinMode (5, OUTPUT);
digitalWrite(7, 0);
pinMode (7, OUTPUT);
digitalWrite(9, 0);
ledcSetup(0, 1000, 8);
ledcAttachPin(3, 0);
ledcSetup(1, 1000, 8);
ledcAttachPin(7, 1);
// ledcSetup(3, 50, 16);
// ledcAttachPin(13, 3);
// ledcSetup(4, 50, 16);
// ledcAttachPin(15, 4);
init_copter();
delay(100);
}
void loop() {
loop_400Hz();
}
/*
* MIT License
*
* Copyright (c) 2024 Kouhei Ito
* Copyright (c) 2025 Jun Kataoka
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
//Controller for M5Fly
//#define DEBUG
#include <Arduino.h>
#include <M5AtomS3.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <MPU6886.h>
#include <MadgwickAHRS.h>
#include <atoms3joy.h>
#include <FS.h>
#include <SPIFFS.h>
#include <driver/ledc.h>
#define NOTE_D1 294
#define NOTE_D2 330
#define NOTE_D3 350
#define NOTE_D4 393
#define NOTE_D5 441
#define NOTE_D6 495
#define NOTE_D7 556
const int buzzerPin = 5;
const int channel = 0;
void setup_pwm_buzzer(void);
void beep(void);
void start_tone(void);
void buzzer_sound(uint32_t frequency, uint32_t duration_ms);
#define CHANNEL 1
#define ROTATING 1
#define NOT_ROTATING 0
#define ROTATING_W_LOG 2
#define NOT_ROTATING_W_LOG 3
#define FIRE_CONTROL_MODE 1
#define NOT_FIRE_CONTROL_MODE 0
#define RESO10BIT (4096)
esp_now_peer_info_t peerInfo;
int counter1 =0, counter2 =0, move =0;
bool send_esp_flag = true;
bool toio_swap_flag = true;
int toio_a, toio_b;
float Throttle;
float aileron, elevator, rudder;
uint16_t aileron_bias =2048;
uint16_t elevator_bias = 2048;
uint16_t rudder_bias =2048;
uint16_t Throttle_bias = 2048;
short xstick=0;
short ystick=0;
uint8_t rotation=NOT_ROTATING;
uint8_t fire=NOT_FIRE_CONTROL_MODE;
volatile uint8_t Loop_flag=0;
float Timer = 0.0;
float dTime = 0.01;
uint8_t Timer_state = 0;
uint8_t StickMode = 2;
uint32_t espnow_version;
volatile uint8_t proactive_flag = 0;
unsigned long stime,etime,dtime;
uint8_t axp_cnt=0;
uint8_t is_peering=0;
uint8_t senddata[25];//19->22->23->24->25
uint8_t disp_counter=0;
//StampFly MAC ADDRESS
//1 F4:12:FA:66:80:54 (Yellow)
//2 F4:12:FA:66:77:A4
uint8_t Addr1[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t Addr2[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//Channel
uint8_t Ch_counter;
volatile uint8_t Received_flag = 0;
volatile uint8_t Channel = CHANNEL;
void rc_init(void);
void data_send(void);
void show_battery_info();
void voltage_print(void);
void setup_pwm_buzzer(void) {
ledcSetup(channel, 4000, 8); // 配置PWM通道:通道0,频率3000Hz,分辨率8位
ledcAttachPin(buzzerPin, channel); // 将PWM通道绑定到GPIO
}
void buzzer_sound(uint32_t frequency, uint32_t duration_ms) {
ledcWriteTone(channel, frequency);
ledcWrite(channel, 64);
vTaskDelay(duration_ms / portTICK_PERIOD_MS);
ledcWriteTone(channel, 0);
digitalWrite(channel, 0);
}
void beep(void) {
buzzer_sound(4000, 100);
}
void start_tone(void) {
buzzer_sound(2000, 100);
buzzer_sound(1000, 100);
#if 0
buzzer_sound(NOTE_D1, 200);
buzzer_sound(NOTE_D5, 200);
buzzer_sound(NOTE_D3, 200);
buzzer_sound(NOTE_D4, 200);
#endif
}
// 受信コールバック
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *recv_data, int data_len)
{
if (is_peering) {
if (recv_data[7] == 0xaa && recv_data[8] == 0x55 && recv_data[9] == 0x16 && recv_data[10] == 0x88) {
Received_flag = 1;
Channel = recv_data[0];
Addr2[0] = recv_data[1];
Addr2[1] = recv_data[2];
Addr2[2] = recv_data[3];
Addr2[3] = recv_data[4];
Addr2[4] = recv_data[5];
Addr2[5] = recv_data[6];
USBSerial.printf("Receive !\n");
}
}
else {
//データ受信時に実行したい内容をここに書く。
float a;
uint8_t *dummy;
uint8_t offset = 2;
//Channel_detected_flag++;
//if(Channel_detected_flag>10)Channel_detected_flag=10;
//Serial.printf("Channel=%d ",Channel);
dummy=(uint8_t*)&a;
dummy[0]=recv_data[0];
dummy[1]=recv_data[1];
if (dummy[0]==0xF4)return;
if ((dummy[0]==99)&&(dummy[1]==99))Serial.printf("#PID Gain P Ti Td Eta ");
for (uint8_t i=0; i<((data_len-offset)/4); i++)
{
dummy[0]=recv_data[i*4 + 0 + offset];
dummy[1]=recv_data[i*4 + 1 + offset];
dummy[2]=recv_data[i*4 + 2 + offset];
dummy[3]=recv_data[i*4 + 3 + offset];
// USBSerial.printf("%9.4f ", a);
}
// USBSerial.printf("\r\n");
}
}
#define BUF_SIZE 128
// EEPROMにデータを保存する
void save_data(void)
{
SPIFFS.begin(true);
/* CREATE FILE */
File fp = SPIFFS.open("/peer_info.txt", FILE_WRITE); // 書き込み、存在すれば上書き
char buf[BUF_SIZE + 1];
sprintf(buf, "%d,%02X,%02X,%02X,%02X,%02X,%02X",
Channel,
Addr2[0],
Addr2[1],
Addr2[2],
Addr2[3],
Addr2[4],
Addr2[5]);
fp.write((uint8_t *)buf, BUF_SIZE);
fp.close();
SPIFFS.end();
// USBSerial.printf("Saved Data:%d,[%02X:%02X:%02X:%02X:%02X:%02X]",
// Channel,
// Addr2[0],
// Addr2[1],
// Addr2[2],
// Addr2[3],
// Addr2[4],
// Addr2[5]);
}
// EEPROMからデータを読み出す
void load_data(void)
{
SPIFFS.begin(true);
File fp = SPIFFS.open("/peer_info.txt", FILE_READ);
char buf[BUF_SIZE + 1];
while (fp.read((uint8_t *)buf, BUF_SIZE) == BUF_SIZE)
{
//USBSerial.print(buf);
sscanf(buf,"%hhd,%hhX,%hhX,%hhX,%hhX,%hhX,%hhX",
&Channel,
&Addr2[0],
&Addr2[1],
&Addr2[2],
&Addr2[3],
&Addr2[4],
&Addr2[5]);
// USBSerial.printf("%d,%02X,%02X,%02X,%02X,%02X,%02X\n\r",
// Channel,
// Addr2[0],
// Addr2[1],
// Addr2[2],
// Addr2[3],
// Addr2[4],
// Addr2[5]);
}
fp.close();
SPIFFS.end();
}
void rc_init(uint8_t ch, uint8_t* addr)
{
// ESP-NOW初期化
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
esp_now_unregister_recv_cb();
esp_now_register_recv_cb(OnDataRecv);
USBSerial.println("ESPNow Init Success");
} else {
USBSerial.println("ESPNow Init Failed");
ESP.restart();
}
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, addr, 6);
peerInfo.channel = ch;
peerInfo.encrypt = false;
uint8_t peer_mac_addre;
while (esp_now_add_peer(&peerInfo) != ESP_OK) {
USBSerial.println("Failed to add peer");
}
esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
}
void peering(void)
{
uint8_t break_flag;
uint32_t beep_delay = 0;
//StampFlyはMACアドレスをFF:FF:FF:FF:FF:FFとして
//StampFlyのMACアドレスをでブロードキャストする
//その際にChannelが機体と送信機で同一でない場合は受け取れない
// ESP-NOWコールバック登録
esp_now_register_recv_cb(OnDataRecv);
//ペアリング
Ch_counter = 1;
while(1)
{
USBSerial.printf("Try channel %02d.\n\r", Ch_counter);
peerInfo.channel = Ch_counter;
peerInfo.encrypt = false;
while (esp_now_mod_peer(&peerInfo) != ESP_OK)
{
USBSerial.println("Failed to mod peer");
}
esp_wifi_set_channel(Ch_counter, WIFI_SECOND_CHAN_NONE);
//Wait receive StampFly MAC Address
//uint32_t counter=1;
//Channelをひとつづつ試していく
for (uint8_t i =0;i<100;i++)
{
break_flag = 0;
if (Received_flag == 1)
{
break_flag = 1;
break;
}
usleep(100);
}
if (millis() - beep_delay >= 500) {
beep();
beep_delay = millis();
}
if (break_flag)break;
Ch_counter++;
if(Ch_counter==15)Ch_counter=1;
}
//Channel = Ch_counter;
save_data();
is_peering = 0;
// USBSerial.printf("Channel:%02d\n\r", Channel);
// USBSerial.printf("MAC2:%02X:%02X:%02X:%02X:%02X:%02X:\n\r",
// Addr2[0],Addr2[1],Addr2[2],Addr2[3],Addr2[4],Addr2[5]);
// USBSerial.printf("MAC1:%02X:%02X:%02X:%02X:%02X:%02X:\n\r",
// Addr1[0],Addr1[1],Addr1[2],Addr1[3],Addr1[4],Addr1[5]);
//Peering
while (esp_now_del_peer(Addr1) != ESP_OK) {
Serial.println("Failed to delete peer1");
}
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, Addr2, 6);//Addr1->Addr2 ////////////////////////////
peerInfo.channel = Channel;
peerInfo.encrypt = false;
while (esp_now_add_peer(&peerInfo) != ESP_OK)
{
USBSerial.println("Failed to add peer2");
}
esp_wifi_set_channel(Channel, WIFI_SECOND_CHAN_NONE);
}
void change_channel(uint8_t ch)
{
peerInfo.channel = ch;
if (esp_now_mod_peer(&peerInfo)!=ESP_OK)
{
USBSerial.println("Failed to modify peer");
return;
}
esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
}
//周期カウンタ割り込み関数
hw_timer_t * timer = NULL;
void IRAM_ATTR onTimer()
{
Loop_flag = 1;
//Timer = Timer + dTime;
}
void setup() {
M5.begin();
Serial2.begin(115200, SERIAL_8N1, 1, 2);
Wire1.begin(38, 39, 400*1000);
load_data();
M5.update();
setup_pwm_buzzer();
M5.Lcd.setRotation( 2 );
M5.Lcd.setTextFont(2);
M5.Lcd.setCursor(4, 2);
if (M5.Btn.isPressed() || (Addr2[0] == 0xFF && Addr2[1] == 0xFF && Addr2[2] == 0xFF && Addr2[3] == 0xFF &&
Addr2[4] == 0xFF && Addr2[5] == 0xFF)) {
M5.Lcd.println("Push LCD panel!");
while (1) {
M5.update();
if (M5.Btn.wasPressed()) {
is_peering = 1;
break;
}
}
rc_init(1, Addr1);
USBSerial.printf("Button pressed!\n\r");
M5.Lcd.println(" ");
M5.Lcd.println("Push StampFly");
M5.Lcd.println(" Reset Button!");
M5.Lcd.println(" ");
M5.Lcd.println("Pairing...");
peering();
}
else rc_init(Channel, Addr2);
M5.Lcd.fillScreen(BLACK);
joy_update();
StickMode = 2;
if(getOptionButton())
{
StickMode = 3;
M5.Lcd.println("Please release button.");
while(getOptionButton())joy_update();
}
fire =NOT_FIRE_CONTROL_MODE;
delay(500);
if (StickMode == 3)
{
THROTTLE = RIGHTY;
AILERON = LEFTX;
ELEVATOR = LEFTY;
RUDDER = RIGHTX;
ARM_BUTTON = RIGHT_STICK_BUTTON;
FLIP_BUTTON = LEFT_STICK_BUTTON;
MODE_BUTTON = RIGHT_BUTTON;
OPTION_BUTTON = LEFT_BUTTON;
}
else
{
THROTTLE = LEFTY;
AILERON = RIGHTX;
ELEVATOR = RIGHTY;
RUDDER = LEFTX;
ARM_BUTTON = LEFT_STICK_BUTTON;
FLIP_BUTTON = RIGHT_STICK_BUTTON;
MODE_BUTTON = RIGHT_BUTTON;
OPTION_BUTTON = LEFT_BUTTON;
}
byte error, address;
int nDevices;
////////////////////////////////////////////////////////
USBSerial.println("Scanning... Wire1");
nDevices = 0;
for (address = 1; address < 127; address++ )
{
Wire1.beginTransmission(address);
error = Wire1.endTransmission();
if (error == 0)
{
USBSerial.print("I2C device found at address 0x");
if (address < 16)
USBSerial.print("0");
USBSerial.print(address, HEX);
USBSerial.println(" !");
nDevices++;
}
else if (error == 4)
{
USBSerial.print("Unknown error at address 0x");
if (address < 16)
USBSerial.print("0");
USBSerial.println(address, HEX);
}
}
if (nDevices == 0)
USBSerial.println("No I2C devices found\n");
else
USBSerial.println("done\n");
esp_now_get_version(&espnow_version);
USBSerial.printf("ESP-NOW Version %d\n", espnow_version);
//割り込み設定
timer = timerBegin(1, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 10000, true);
timerAlarmEnable(timer);
delay(100);
}
uint8_t check_rotation_mode_change(void)
{
uint8_t state;
static uint8_t flag =0;
state = 0;
if (flag==0)
{
if (getModeButton() == 1)
{
flag = 1;
}
}
else
{
if (getModeButton() == 0)
{
flag = 0;
state = 1;
}
}
//USBSerial.printf("%d %d\n\r", state, flag);
return state;
}
uint8_t check_fire_mode_change(void)
{
uint8_t state;
static uint8_t flag =0;
state = 0;
if (flag==0)
{
if (getOptionButton() == 1)
{
flag = 1;
}
}
else
{
if (getOptionButton() == 0)
{
flag = 0;
state = 1;
}
}
//USBSerial.printf("%d %d\n\r", state, flag);
return state;
}
void loop() {
uint16_t _throttle;// = getThrottle();
uint16_t _aileron;// = getAileron();
uint16_t _elevator;// = getElevator();
uint16_t _rudder;// = getRudder();
while(Loop_flag==0);
Loop_flag = 0;
etime = stime;
stime = micros();
dtime = stime - etime;
M5.update();
joy_update();
//Stop Watch Start&Stop&Reset
if(M5.Btn.wasPressed()==true)
{
if (Timer_state == 0)Timer_state = 1;
else if (Timer_state == 1)Timer_state = 0;
}
if(M5.Btn.pressedFor(400)==true)
{
Timer_state = 2;
}
if (Timer_state == 1)
{
//カウントアップ
Timer = Timer + dTime;
}
else if (Timer_state == 2)
{
//タイマリセット
Timer = 0.0;
Timer_state = 0;
}
if (check_rotation_mode_change() == 1)
{
if (rotation==ROTATING)rotation=NOT_ROTATING;
else rotation = ROTATING;
}
if (check_fire_mode_change() == 1)
{
if (fire==FIRE_CONTROL_MODE)fire=NOT_FIRE_CONTROL_MODE;
else fire = FIRE_CONTROL_MODE;
}
_throttle = getThrottle();
_aileron = getAileron();
_elevator = getElevator();
_rudder = getRudder();
if(getArmButton()==1)
{
//Throttle_bias = _throttle;
aileron_bias = _aileron;
elevator_bias = _elevator;
rudder_bias = _rudder;
}
//量産版
Throttle = -(float)(_throttle - Throttle_bias)/(float)(RESO10BIT*0.5);
aileron = (float)(_aileron - aileron_bias)/(float)(RESO10BIT*0.5);
elevator = (float)(_elevator - elevator_bias)/(float)(RESO10BIT*0.5);
rudder = (float)(_rudder - rudder_bias)/(float)(RESO10BIT*0.5);
Throttle = Throttle + 0.02;
aileron = aileron - 0.04;
elevator = elevator + 0.01;
rudder = rudder -0.05;
//********************************* */
// USBSerial.printf("%1.3f %1.3f %1.3f %1.3f \r\n", Throttle, aileron, elevator, rudder);
//uart1.write('1;1;'+rd+';'+str(rs)+';'+ld+';'+str(ls)+"\n")
int ld = 0, rd = 0, ls, rs, ols, ors, lls, rrs, olls, orrs, rrd, lld;
if(toio_swap_flag == true){
toio_a = 1;
toio_b = 2;
}else{
toio_a = 2;
toio_b = 1;
}
rs = (elevator * 100) - (rudder * 30);
ls = (elevator * 100) + (rudder * 30);
if(rs < 0){rd = 1;rs = -rs;}
if(ls < 0){ld = 1;ls = -ls;}
// toio control
if(counter1 > 1 && (ors != rs || ols != ls)){
Serial2.printf("%d;1;%d;%d;%d;%d;\r\n", toio_a, rd, rs, ld, ls); //toio command
USBSerial.printf("%d %d %d %d %d %d %d %d\r\n", rd, ld, rs, ls, rotation, getOptionButton(),
getArmButton(), getFlipButton());
counter1 = 0;
}
ols = ls;
ors = rs;
counter1++;
// if(getOptionButton() == 1)fire = 1;
if((getArmButton() == 1 || move != 0) && fire == 0){
switch (move){
case 0:
// Serial2.printf("toio_b;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", 1, 5, 0, 10, 0, 65535, 65535, 0); //toio command
Serial2.printf("%d;1;%d;%d;%d;%d;\r\n", toio_a, 0, 20, 0, 20); //toio command
delay(1000);
Serial2.printf("%d;1;%d;%d;%d;%d;\r\n", toio_a, 0, 0, 0, 0); //toio command
move++;
break;
case 1:
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 240); //toio command
delay(1000);
fire = 1;
move++;
break;
case 2:
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 255); //toio command
delay(1000);
fire = 1;
move++;
break;
case 3:
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 270); //toio command
delay(1000);
fire = 1;
move++;
break;
case 4:
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 285); //toio command
delay(1000);
fire = 1;
move++;
break;
case 5:
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 300); //toio command
delay(1000);
fire = 1;
move++;
break;
default:
move = 0;
Serial2.printf("%d;3;%d;%d;%d;%d;%d;%d;%d;%d;\r\n", toio_b, 1, 5, 0, 10, 0, 65535, 65535, 270); //toio command
delay(1000);
break;
}
}
if(fire == 1){
if(counter2 > 20 ){
fire =0;
counter2 = 0;
}else{
counter2++;
}
}
if(Throttle > 0.9){
send_esp_flag = !send_esp_flag;
delay(500);
}
if(Throttle < -0.9){
toio_swap_flag = !toio_swap_flag;
delay(500);
}
//********************************* */
uint8_t* d_int;
//ブロードキャストの混信を防止するためこの機体のMACアドレスに送られてきたものか判断する
senddata[0] = peerInfo.peer_addr[3];////////////////////////////
senddata[1] = peerInfo.peer_addr[4];////////////////////////////
senddata[2] = peerInfo.peer_addr[5];////////////////////////////
d_int = (uint8_t*)&rudder;
senddata[3]=d_int[0];
senddata[4]=d_int[1];
senddata[5]=d_int[2];
senddata[6]=d_int[3];
d_int = (uint8_t*)&Throttle;
senddata[7]=d_int[0];
senddata[8]=d_int[1];
senddata[9]=d_int[2];
senddata[10]=d_int[3];
d_int = (uint8_t*)&aileron;
senddata[11]=d_int[0];
senddata[12]=d_int[1];
senddata[13]=d_int[2];
senddata[14]=d_int[3];
d_int = (uint8_t*)&elevator;
senddata[15]=d_int[0];
senddata[16]=d_int[1];
senddata[17]=d_int[2];
senddata[18]=d_int[3];
senddata[19]=getArmButton();
senddata[20]=getFlipButton();
senddata[21]=rotation; //rotation
senddata[22]=fire; //fire
senddata[23]=proactive_flag;
//checksum
senddata[24]=0;
for(uint8_t i=0;i<24;i++)senddata[24]=senddata[24]+senddata[i];
//送信
if(send_esp_flag == true){
esp_err_t result = esp_now_send(peerInfo.peer_addr, senddata, sizeof(senddata));
}
// #ifdef DEBUG
// USBSerial.printf("%02X:%02X:%02X:%02X:%02X:%02X\n",
// peerInfo.peer_addr[0],
// peerInfo.peer_addr[1],
// peerInfo.peer_addr[2],
// peerInfo.peer_addr[3],
// peerInfo.peer_addr[4],
// peerInfo.peer_addr[5]);
// #endif
//Display information
//float vbat =0.0;// M5.Axp.GetBatVoltage();
//int8_t bat_charge_p = int8_t((vbat - 3.0) / 1.2 * 100);
M5.Lcd.setCursor(4, 2+disp_counter*17);
switch (disp_counter)
{
case 0:
M5.Lcd.printf("MAC ADR %02X:%02X ", peerInfo.peer_addr[4],peerInfo.peer_addr[5]);
break;
case 1:
M5.Lcd.printf("esp_now: %s ", send_esp_flag ? "true" : "false");
// M5.Lcd.printf("BAT 1:%4.1f 2:%4.1f", Battery_voltage[0],Battery_voltage[1]);
//M5.Lcd.printf("X:%4d",xstick);
break;
case 2:
M5.Lcd.printf("toio_swap: %s ", toio_swap_flag ? "1 2" : "2 1");
break;
case 3:
M5.Lcd.printf("CHL: %02d",peerInfo.channel);
break;
case 4:
if( fire == FIRE_CONTROL_MODE ) M5.Lcd.printf("fire ");
else if ( fire == NOT_FIRE_CONTROL_MODE ) M5.Lcd.printf("not fire");
break;
case 5:
if( rotation == ROTATING ) M5.Lcd.printf("rotating ");
else if ( rotation == NOT_ROTATING ) M5.Lcd.printf("not rotating");
break;
case 6:
M5.Lcd.printf("Time:%7.2f",Timer);
break;
case 7:
break;
case 8:
break;
case 9:
break;
}
disp_counter++;
if(disp_counter==11)disp_counter=0;
//Reset
if( /*M5.Axp.GetBtnPress() == 2*/ 0 ){
// 電源ボタンクリック
//M5.Lcd.println("AtomFly2.0");
esp_restart();
}
}
void show_battery_info(){
#if 0
// バッテリー電圧表示
double vbat = 0.0;
int8_t bat_charge_p = 0;
vbat = M5.Axp.GetBatVoltage();
M5.Lcd.setCursor(5, 100);
//M5.Lcd.setTextSize(1);
M5.Lcd.printf("Volt:\n %8.2fV", vbat);
// バッテリー残量表示
bat_charge_p = int8_t((vbat - 3.0) / 1.2 * 100);
M5.Lcd.setCursor(5, 140);
M5.Lcd.printf("Charge:\n %8d%%", bat_charge_p);
#endif
}
void voltage_print(void)
{
M5.Lcd.setCursor(0, 17, 2);
M5.Lcd.printf("%3.1fV", Battery_voltage);
}
Comments