├── ELRS Transmitter Case .STLs ├── Cradle (3D Print).stl ├── ELRS Handset Bottom.stl ├── ELRS Handset Top (No Initials).stl ├── Front Foot (3D Print).stl ├── Internal Antenna Mount.stl └── Rear Foot (3D Print).stl ├── Firmware ├── .gitignore ├── README.md ├── dfu-util-0.9-win64.zip ├── include │ ├── README │ ├── lcd-tdisplay │ │ ├── bmp.h │ │ ├── lcd.h │ │ └── oledfont.h │ ├── lcd │ │ ├── bmp.h │ │ ├── lcd.h │ │ └── oledfont.h │ └── systick.h ├── lib │ ├── 1AUDfilter │ │ ├── 1AUDfilter.cpp │ │ ├── 1AUDfilter.h │ │ ├── 1AUDfilterInt.cpp │ │ └── 1AUDfilterInt.h │ ├── CRC │ │ ├── crc.cpp │ │ └── crc.h │ ├── README │ └── SX1280Driver │ │ ├── SX1280.cpp │ │ ├── SX1280.h │ │ ├── SX1280Driver.h │ │ ├── SX1280_Regs.h │ │ ├── SX1280_hal.cpp │ │ └── SX1280_hal.h ├── platformio.ini ├── src │ ├── FHSS.cpp │ ├── FHSS.h │ ├── Serial.cpp │ ├── Serial.h │ ├── SimpleStore.cpp │ ├── SimpleStore.h │ ├── common.cpp │ ├── common.h │ ├── config.h │ ├── lcd-tdisplay │ │ └── lcd.c │ ├── lcd │ │ └── lcd.c │ ├── main.cpp │ ├── systick.c │ ├── utils.cpp │ └── utils.h └── test │ └── README ├── LICENSE ├── PCB Gerbers ├── ExLRS_Filter_Board.zip ├── ExLRS_Handset_Main_PCB.zip └── RF_Carrier.zip ├── Wiki ├── 3D Printing │ └── 3D Printing Notes.docx ├── BOM - Bill of Materials │ ├── ELRS Handset Component List.xlsx │ ├── ExLRS Handset BOM - AliExpress.docx │ └── ExLRS Handset BOM - Digi-Key.xlsx ├── Encoder │ └── ExLRS Handset Encoder.docx ├── Filter Boards │ ├── 20210325_175824414_iOS.jpg │ ├── 20210325_214350122_iOS.jpg │ ├── 20210325_214425201_iOS.jpg │ ├── 20210325_224344439_iOS.jpg │ └── ELRS Handset Filter Boards.docx ├── Main Board Assembly │ ├── 20210325_010320095_iOS.jpg │ ├── 20210325_175546467_iOS.heic │ ├── 20210325_175743894_iOS.heic │ ├── 20210327_000439400_iOS.jpg │ ├── 20210327_000455407_iOS.jpg │ ├── 20210327_000506065_iOS.jpg │ ├── 20210327_000602607_iOS.jpg │ └── Main PCB.jpg ├── Micro Controller Unit │ ├── 20210327_000439400_iOS.jpg │ └── Micro Controller Unit.docx ├── Platform IO Linux Instructions │ ├── 20210402_213124126_iOS.jpg │ └── Platform IO Notes.docx ├── RF Boards │ ├── 20210402_183141254_iOS.jpg │ ├── 20210402_183323582_iOS.jpg │ ├── 20210402_183400175_iOS.jpg │ ├── 20210402_183413119_iOS.jpg │ ├── 20210402_183540130_iOS.jpg │ └── RF Board Notes.docx ├── Wireless Charging Cradle │ ├── 20210328_185023077_iOS.jpg │ ├── 20210328_185138206_iOS.jpg │ ├── 20210328_185158322_iOS.jpg │ ├── 20210328_185213217_iOS.jpg │ ├── 20210328_190506651_iOS.jpg │ └── 20210410_000010165_iOS.jpg ├── images │ ├── filter_cheat_sheet.png │ └── pcbs │ │ ├── filter_bottom.jpg │ │ ├── filter_top.jpg │ │ ├── main_component_side.jpg │ │ ├── main_lcd_side.jpg │ │ ├── rc_adapter_bottom.jpg │ │ └── rc_adapter_top.jpg └── notes │ ├── Compiling-for-RISC-V.md │ ├── Filter-Module.md │ ├── Main-PCB.md │ └── Radio-adapter-PCB.md └── readme.md /ELRS Transmitter Case .STLs/Cradle (3D Print).stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/Cradle (3D Print).stl -------------------------------------------------------------------------------- /ELRS Transmitter Case .STLs/ELRS Handset Bottom.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/ELRS Handset Bottom.stl -------------------------------------------------------------------------------- /ELRS Transmitter Case .STLs/ELRS Handset Top (No Initials).stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/ELRS Handset Top (No Initials).stl -------------------------------------------------------------------------------- /ELRS Transmitter Case .STLs/Front Foot (3D Print).stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/Front Foot (3D Print).stl -------------------------------------------------------------------------------- /ELRS Transmitter Case .STLs/Internal Antenna Mount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/Internal Antenna Mount.stl -------------------------------------------------------------------------------- /ELRS Transmitter Case .STLs/Rear Foot (3D Print).stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/ELRS Transmitter Case .STLs/Rear Foot (3D Print).stl -------------------------------------------------------------------------------- /Firmware/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /Firmware/README.md: -------------------------------------------------------------------------------- 1 | # Express_CX 2 | Firmware for an experimental handset based on ExpressLRS 3 | 4 | You're welcome to look around, but the project is still very much a work in progress and isn't particularly consumable yet. It's purpose is to test the limits of current RC technology and is likely only of interest to a very small subset of pilots. 5 | 6 | This project builds on the awesome ExpressLRS high performance RC link https://github.com/AlessandroAU/ExpressLRS 7 | 8 | The idea is to try and provide the highest quality RC data to the flight controller, building on the low latency, low jitter capabilities of ExpressLRS. The primary target is quadcopters running BetaFlight - this is not intended to be a general purpose RC controller. 9 | 10 | The gimbal data is filtered before being sampled at high (currently 128kHz per channel) frequency and then run through the 1AUD filter designed by Chris Thompson https://github.com/ctzsnooze/1AUD-filter. This allows customisable filter settngs to be applied for different use cases. Latency is reduced through increased packet rate, currently up to 1kHz. 11 | 12 | In future I hope to experiment with calculating the first and second order derivatives for each channel and sending those over the link along with the absolute stick positions. 13 | -------------------------------------------------------------------------------- /Firmware/dfu-util-0.9-win64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Firmware/dfu-util-0.9-win64.zip -------------------------------------------------------------------------------- /Firmware/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /Firmware/include/lcd-tdisplay/lcd.h: -------------------------------------------------------------------------------- 1 | #ifndef __LCD_H 2 | #define __LCD_H 3 | 4 | #include "systick.h" 5 | #include "stdlib.h" 6 | #include "gd32vf103_gpio.h" 7 | 8 | #define USE_HORIZONTAL 0 //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏 9 | #define HAS_BLK_CNTL 0 10 | 11 | #if USE_HORIZONTAL==0||USE_HORIZONTAL==2 12 | #define LCD_W 240 13 | #define LCD_H 135 14 | #else 15 | #define LCD_W 135 16 | #define LCD_H 240 17 | #endif 18 | 19 | typedef unsigned char u8; 20 | typedef unsigned int u16; 21 | typedef unsigned long u32; 22 | 23 | // #define LED_ON gpio_bit_reset(GPIOC,GPIO_PIN_13) 24 | // #define LED_OFF gpio_bit_set(GPIOC,GPIO_PIN_13) 25 | 26 | #define LED_ON 27 | #define LED_OFF 28 | 29 | #define SPI0_CFG 1 //hardware spi 30 | // #define SPI0_CFG 2 //hardware spi dma 31 | // #define SPI0_CFG 3 //software spi 32 | 33 | #define FRAME_SIZE 25600 34 | 35 | //-----------------OLED端口定义---------------- 36 | #if SPI0_CFG == 1 37 | #define OLED_SCLK_Clr() 38 | #define OLED_SCLK_Set() 39 | 40 | #define OLED_SDIN_Clr() 41 | #define OLED_SDIN_Set() 42 | 43 | #define OLED_CS_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_2) //CS PB2 44 | #define OLED_CS_Set() gpio_bit_set(GPIOB,GPIO_PIN_2) 45 | #elif SPI0_CFG == 2 46 | #define OLED_SCLK_Clr() 47 | #define OLED_SCLK_Set() 48 | 49 | #define OLED_SDIN_Clr() 50 | #define OLED_SDIN_Set() 51 | 52 | #define OLED_CS_Clr() 53 | #define OLED_CS_Set() 54 | #else /* SPI0_CFG */ 55 | #define OLED_SCLK_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_5) //CLK PA5 56 | #define OLED_SCLK_Set() gpio_bit_set(GPIOA,GPIO_PIN_5) 57 | 58 | #define OLED_SDIN_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_7) //DIN PA7 59 | #define OLED_SDIN_Set() gpio_bit_set(GPIOA,GPIO_PIN_7) 60 | 61 | #define OLED_CS_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_2) //CS PB2 62 | #define OLED_CS_Set() gpio_bit_set(GPIOB,GPIO_PIN_2) 63 | #endif /* SPI0_CFG */ 64 | 65 | #define OLED_RST_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_1) //RES PB1 66 | #define OLED_RST_Set() gpio_bit_set(GPIOB,GPIO_PIN_1) 67 | 68 | #define OLED_DC_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_0) //DC PB0 69 | #define OLED_DC_Set() gpio_bit_set(GPIOB,GPIO_PIN_0) 70 | 71 | 72 | #if HAS_BLK_CNTL 73 | #define OLED_BLK_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_5)//BLK 74 | #define OLED_BLK_Set() gpio_bit_set(GPIOA,GPIO_PIN_5) 75 | #else 76 | #define OLED_BLK_Clr() 77 | #define OLED_BLK_Set() 78 | #endif 79 | 80 | #define OLED_CMD 0 //写命令 81 | #define OLED_DATA 1 //写数据 82 | 83 | extern u16 BACK_COLOR; //背景色 84 | 85 | void setRotation(uint8_t m); 86 | void LCD_Writ_Bus(u8 dat); 87 | void LCD_WR_DATA8(u8 dat); 88 | void LCD_WR_DATA(u16 dat); 89 | void LCD_WR_REG(u8 dat); 90 | void LCD_Address_Set(u16 x1, u16 y1, u16 x2, u16 y2); 91 | void Lcd_Init(void); 92 | void LCD_Clear(u16 Color); 93 | void LCD_ShowChinese(u16 x, u16 y, u8 index, u8 size, u16 color); 94 | void LCD_DrawPoint(u16 x, u16 y, u16 color); 95 | void LCD_DrawPoint_big(u16 x, u16 y, u16 color); 96 | void LCD_Fill(u16 xsta, u16 ysta, u16 xend, u16 yend, u16 color); 97 | void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2, u16 color); 98 | void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color); 99 | void Draw_Circle(u16 x0, u16 y0, u8 r, u16 color); 100 | void LCD_ShowChar(u16 x, u16 y, u8 num, u8 mode, u16 color); 101 | void LCD_ShowString(u16 x, u16 y, const u8 *p, u16 color); 102 | u32 mypow(u8 m, u8 n); 103 | void LCD_ShowNum(u16 x, u16 y, u16 num, u8 len, u16 color); 104 | void LCD_ShowNum1(u16 x, u16 y, float num, u8 len, u16 color); 105 | void LCD_ShowPicture(u16 x1, u16 y1, u16 x2, u16 y2); 106 | void LCD_ShowLogo(void); 107 | 108 | 109 | //画笔颜色 110 | #define WHITE 0xFFFF 111 | #define BLACK 0x0000 112 | #define BLUE 0x001F 113 | #define BRED 0XF81F 114 | #define GRED 0XFFE0 115 | #define GBLUE 0X07FF 116 | #define RED 0xF800 117 | #define MAGENTA 0xF81F 118 | #define GREEN 0x07E0 119 | #define CYAN 0x7FFF 120 | #define YELLOW 0xFFE0 121 | #define BROWN 0XBC40 //棕色 122 | #define BRRED 0XFC07 //棕红色 123 | #define GRAY 0X8430 //灰色 124 | //GUI颜色 125 | 126 | #define DARKBLUE 0X01CF //深蓝色 127 | #define LIGHTBLUE 0X7D7C //浅蓝色 128 | #define GRAYBLUE 0X5458 //灰蓝色 129 | //以上三色为PANEL的颜色 130 | 131 | #define LIGHTGREEN 0X841F //浅绿色 132 | #define LGRAY 0XC618 //浅灰色(PANNEL),窗体背景色 133 | 134 | #define LGRAYBLUE 0XA651 //浅灰蓝色(中间层颜色) 135 | #define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色) 136 | 137 | 138 | 139 | #endif 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /Firmware/include/lcd-tdisplay/oledfont.h: -------------------------------------------------------------------------------- 1 | #ifndef __OLEDFONT_H 2 | #define __OLEDFONT_H 3 | //常用ASCII表 4 | //偏移量32 5 | //ASCII字符集 6 | //偏移量32 7 | //大小:12*6 8 | typedef unsigned char u8; 9 | typedef unsigned int u16; 10 | typedef unsigned long u32; 11 | 12 | /************************************6*8的点阵************************************/ 13 | 14 | 15 | /****************************************32*32的点阵************************************/ 16 | // u8 Hzk32[]={ 17 | 18 | // 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x01,0x00, 19 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 20 | // 0x10,0x80,0x01,0x0C,0xF0,0xFF,0xFF,0x0F,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04, 21 | // 0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04, 22 | // 0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0xF0,0xFF,0xFF,0x07,0x30,0x80,0x01,0x04, 23 | // 0x30,0x80,0x01,0x04,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 24 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 25 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,/*"中",0*/ 26 | // 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01, 27 | // 0x00,0xFF,0xFF,0x01,0x00,0x03,0x80,0x01,0x00,0x03,0x80,0x01,0x00,0xFF,0xFF,0x01, 28 | // 0x00,0x03,0x80,0x01,0x00,0x03,0x80,0x01,0x00,0xFF,0xFF,0x01,0x00,0x81,0x81,0x00, 29 | // 0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x1C,0xFC,0xFF,0xFF,0x3F,0x00,0x00,0x40,0x00, 30 | // 0x00,0x03,0xC0,0x01,0x00,0xFF,0xFF,0x01,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00, 31 | // 0x00,0x03,0xC0,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x41,0x00,0x00,0x04,0x01,0x00, 32 | // 0x00,0x0E,0x71,0x00,0x00,0x07,0x81,0x01,0x80,0x01,0x01,0x07,0x60,0x00,0x01,0x0E, 33 | // 0x18,0x98,0x01,0x1C,0x04,0xE0,0x01,0x18,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,/*"景",1*/ 34 | // 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xF0,0xFF,0xFF,0x1F, 35 | // 0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x10,0x0C,0x30,0xFE,0x3F,0x0C, 36 | // 0x30,0x04,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C, 37 | // 0x30,0x00,0xC0,0x0C,0xF0,0xFF,0xFF,0x0D,0x30,0x30,0x06,0x0E,0x30,0x30,0x06,0x0C, 38 | // 0x30,0x10,0x06,0x0C,0x30,0x10,0x06,0x0C,0x30,0x10,0x06,0x0C,0x30,0x18,0x86,0x0C, 39 | // 0x30,0x18,0x86,0x0C,0x30,0x08,0x06,0x0D,0x30,0x0C,0x86,0x0D,0x30,0x06,0xEE,0x0F, 40 | // 0x30,0x02,0xFC,0x0D,0x30,0x01,0x00,0x0C,0xF0,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C, 41 | // 0xF0,0xFF,0xFF,0x0F,0x30,0x00,0x00,0x0C,0x10,0x00,0x00,0x04,0x00,0x00,0x00,0x00,/*"园",2*/ 42 | // 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x01,0x00, 43 | // 0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00, 44 | // 0xE0,0xFF,0xFF,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03, 45 | // 0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0xE0,0xFF,0xFF,0x03, 46 | // 0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03, 47 | // 0x60,0xC0,0x00,0x03,0xE0,0xFF,0xFF,0x03,0x60,0xC0,0x00,0x01,0x60,0xC0,0x00,0x00, 48 | // 0x00,0xC0,0x00,0x08,0x00,0xC0,0x00,0x10,0x00,0xC0,0x00,0x10,0x00,0xC0,0x00,0x18, 49 | // 0x00,0xC0,0x01,0x38,0x00,0x80,0xFF,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"电",3*/ 50 | // 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03, 51 | // 0xC0,0xFF,0xFF,0x07,0x00,0x00,0x80,0x07,0x00,0x00,0xC0,0x00,0x00,0x00,0x60,0x00, 52 | // 0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x80,0x03,0x00,0x00,0x80,0x03,0x00, 53 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x08,0x00,0x80,0x01,0x1C,0xFC,0xFF,0xFF,0x3F, 54 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 55 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 56 | // 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 57 | // 0x00,0xFC,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,/*"子",4*/}; 58 | 59 | const u8 asc2_1608[1520]={ 60 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 61 | 0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x18,0x18,0x00,0x00, 62 | 0x00,0x48,0x6C,0x24,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 63 | 0x00,0x00,0x00,0x24,0x24,0x24,0x7F,0x12,0x12,0x12,0x7F,0x12,0x12,0x12,0x00,0x00, 64 | 0x00,0x00,0x08,0x1C,0x2A,0x2A,0x0A,0x0C,0x18,0x28,0x28,0x2A,0x2A,0x1C,0x08,0x08, 65 | 0x00,0x00,0x00,0x22,0x25,0x15,0x15,0x15,0x2A,0x58,0x54,0x54,0x54,0x22,0x00,0x00, 66 | 0x00,0x00,0x00,0x0C,0x12,0x12,0x12,0x0A,0x76,0x25,0x29,0x11,0x91,0x6E,0x00,0x00, 67 | 0x00,0x06,0x06,0x04,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 68 | 0x00,0x40,0x20,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x10,0x10,0x20,0x40,0x00, 69 | 0x00,0x02,0x04,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x08,0x04,0x02,0x00, 70 | 0x00,0x00,0x00,0x00,0x08,0x08,0x6B,0x1C,0x1C,0x6B,0x08,0x08,0x00,0x00,0x00,0x00, 71 | 0x00,0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x7F,0x08,0x08,0x08,0x08,0x00,0x00,0x00, 72 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x04,0x03, 73 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 74 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x00,0x00, 75 | 0x00,0x00,0x80,0x40,0x40,0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,0x02,0x02,0x00, 76 | 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, 77 | 0x00,0x00,0x00,0x08,0x0E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 78 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x20,0x20,0x10,0x08,0x04,0x42,0x7E,0x00,0x00, 79 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x20,0x18,0x20,0x40,0x40,0x42,0x22,0x1C,0x00,0x00, 80 | 0x00,0x00,0x00,0x20,0x30,0x28,0x24,0x24,0x22,0x22,0x7E,0x20,0x20,0x78,0x00,0x00, 81 | 0x00,0x00,0x00,0x7E,0x02,0x02,0x02,0x1A,0x26,0x40,0x40,0x42,0x22,0x1C,0x00,0x00, 82 | 0x00,0x00,0x00,0x38,0x24,0x02,0x02,0x1A,0x26,0x42,0x42,0x42,0x24,0x18,0x00,0x00, 83 | 0x00,0x00,0x00,0x7E,0x22,0x22,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00, 84 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00, 85 | 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x64,0x58,0x40,0x40,0x24,0x1C,0x00,0x00, 86 | 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, 87 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x04, 88 | 0x00,0x00,0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x40,0x00,0x00, 89 | 0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00, 90 | 0x00,0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x02,0x00,0x00, 91 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x46,0x40,0x20,0x10,0x10,0x00,0x18,0x18,0x00,0x00, 92 | 0x00,0x00,0x00,0x1C,0x22,0x5A,0x55,0x55,0x55,0x55,0x2D,0x42,0x22,0x1C,0x00,0x00, 93 | 0x00,0x00,0x00,0x08,0x08,0x18,0x14,0x14,0x24,0x3C,0x22,0x42,0x42,0xE7,0x00,0x00, 94 | 0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x1E,0x22,0x42,0x42,0x42,0x22,0x1F,0x00,0x00, 95 | 0x00,0x00,0x00,0x7C,0x42,0x42,0x01,0x01,0x01,0x01,0x01,0x42,0x22,0x1C,0x00,0x00, 96 | 0x00,0x00,0x00,0x1F,0x22,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x22,0x1F,0x00,0x00, 97 | 0x00,0x00,0x00,0x3F,0x42,0x12,0x12,0x1E,0x12,0x12,0x02,0x42,0x42,0x3F,0x00,0x00, 98 | 0x00,0x00,0x00,0x3F,0x42,0x12,0x12,0x1E,0x12,0x12,0x02,0x02,0x02,0x07,0x00,0x00, 99 | 0x00,0x00,0x00,0x3C,0x22,0x22,0x01,0x01,0x01,0x71,0x21,0x22,0x22,0x1C,0x00,0x00, 100 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 101 | 0x00,0x00,0x00,0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 102 | 0x00,0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x11,0x0F, 103 | 0x00,0x00,0x00,0x77,0x22,0x12,0x0A,0x0E,0x0A,0x12,0x12,0x22,0x22,0x77,0x00,0x00, 104 | 0x00,0x00,0x00,0x07,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x7F,0x00,0x00, 105 | 0x00,0x00,0x00,0x77,0x36,0x36,0x36,0x36,0x2A,0x2A,0x2A,0x2A,0x2A,0x6B,0x00,0x00, 106 | 0x00,0x00,0x00,0xE3,0x46,0x46,0x4A,0x4A,0x52,0x52,0x52,0x62,0x62,0x47,0x00,0x00, 107 | 0x00,0x00,0x00,0x1C,0x22,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x22,0x1C,0x00,0x00, 108 | 0x00,0x00,0x00,0x3F,0x42,0x42,0x42,0x42,0x3E,0x02,0x02,0x02,0x02,0x07,0x00,0x00, 109 | 0x00,0x00,0x00,0x1C,0x22,0x41,0x41,0x41,0x41,0x41,0x4D,0x53,0x32,0x1C,0x60,0x00, 110 | 0x00,0x00,0x00,0x3F,0x42,0x42,0x42,0x3E,0x12,0x12,0x22,0x22,0x42,0xC7,0x00,0x00, 111 | 0x00,0x00,0x00,0x7C,0x42,0x42,0x02,0x04,0x18,0x20,0x40,0x42,0x42,0x3E,0x00,0x00, 112 | 0x00,0x00,0x00,0x7F,0x49,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00, 113 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, 114 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x22,0x24,0x24,0x14,0x14,0x18,0x08,0x08,0x00,0x00, 115 | 0x00,0x00,0x00,0x6B,0x49,0x49,0x49,0x49,0x55,0x55,0x36,0x22,0x22,0x22,0x00,0x00, 116 | 0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x18,0x18,0x18,0x24,0x24,0x42,0xE7,0x00,0x00, 117 | 0x00,0x00,0x00,0x77,0x22,0x22,0x14,0x14,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00, 118 | 0x00,0x00,0x00,0x7E,0x21,0x20,0x10,0x10,0x08,0x04,0x04,0x42,0x42,0x3F,0x00,0x00, 119 | 0x00,0x78,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x78,0x00, 120 | 0x00,0x00,0x02,0x02,0x04,0x04,0x08,0x08,0x08,0x10,0x10,0x20,0x20,0x20,0x40,0x40, 121 | 0x00,0x1E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x1E,0x00, 122 | 0x00,0x38,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 123 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF, 124 | 0x00,0x06,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 125 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x78,0x44,0x42,0x42,0xFC,0x00,0x00, 126 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x1A,0x26,0x42,0x42,0x42,0x26,0x1A,0x00,0x00, 127 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x44,0x02,0x02,0x02,0x44,0x38,0x00,0x00, 128 | 0x00,0x00,0x00,0x60,0x40,0x40,0x40,0x78,0x44,0x42,0x42,0x42,0x64,0xD8,0x00,0x00, 129 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x7E,0x02,0x02,0x42,0x3C,0x00,0x00, 130 | 0x00,0x00,0x00,0xF0,0x88,0x08,0x08,0x7E,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 131 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x22,0x22,0x1C,0x02,0x3C,0x42,0x42,0x3C, 132 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x3A,0x46,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 133 | 0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 134 | 0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x22,0x1E, 135 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x72,0x12,0x0A,0x16,0x12,0x22,0x77,0x00,0x00, 136 | 0x00,0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 137 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x92,0x92,0x92,0x92,0x92,0xB7,0x00,0x00, 138 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x46,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 139 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, 140 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x26,0x42,0x42,0x42,0x22,0x1E,0x02,0x07, 141 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x44,0x42,0x42,0x42,0x44,0x78,0x40,0xE0, 142 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x4C,0x04,0x04,0x04,0x04,0x1F,0x00,0x00, 143 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x42,0x02,0x3C,0x40,0x42,0x3E,0x00,0x00, 144 | 0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x3E,0x08,0x08,0x08,0x08,0x08,0x30,0x00,0x00, 145 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x42,0x42,0x42,0x42,0x62,0xDC,0x00,0x00, 146 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x14,0x08,0x08,0x00,0x00, 147 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEB,0x49,0x49,0x55,0x55,0x22,0x22,0x00,0x00, 148 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x24,0x18,0x18,0x18,0x24,0x6E,0x00,0x00, 149 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x14,0x18,0x08,0x08,0x07, 150 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x22,0x10,0x08,0x08,0x44,0x7E,0x00,0x00, 151 | 0x00,0xC0,0x20,0x20,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x20,0x20,0xC0,0x00, 152 | 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 153 | 0x00,0x06,0x08,0x08,0x08,0x08,0x08,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x06,0x00, 154 | 0x0C,0x32,0xC2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 155 | }; 156 | 157 | 158 | 159 | 160 | // u8 Hzk16[]={ 161 | 162 | 163 | // 0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,0x84,0x10, 164 | // 0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,/*"中",0*/ 165 | 166 | // 0xF8,0x0F,0x08,0x08,0xF8,0x0F,0x08,0x08,0xF8,0x0F,0x80,0x00,0xFF,0x7F,0x00,0x00, 167 | // 0xF8,0x0F,0x08,0x08,0x08,0x08,0xF8,0x0F,0x80,0x00,0x84,0x10,0xA2,0x20,0x40,0x00,/*"景",1*/ 168 | 169 | // 0x00,0x00,0xFE,0x3F,0x02,0x20,0xF2,0x27,0x02,0x20,0x02,0x20,0xFA,0x2F,0x22,0x21, 170 | // 0x22,0x21,0x22,0x21,0x12,0x29,0x12,0x29,0x0A,0x2E,0x02,0x20,0xFE,0x3F,0x02,0x20,/*"园",2*/ 171 | 172 | // 0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F, 173 | // 0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x50,0x80,0x40,0x80,0x40,0x00,0x7F,/*"电",3*/ 174 | 175 | // 0x00,0x00,0xFE,0x1F,0x00,0x08,0x00,0x04,0x00,0x02,0x80,0x01,0x80,0x00,0xFF,0x7F, 176 | // 0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xA0,0x00,0x40,0x00,/*"子",4*/ 177 | 178 | // 0x10,0x08,0xB8,0x08,0x0F,0x09,0x08,0x09,0x08,0x08,0xBF,0x08,0x08,0x09,0x1C,0x09, 179 | // 0x2C,0x08,0x0A,0x78,0xCA,0x0F,0x09,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,/*"科",5*/ 180 | 181 | // 0x08,0x04,0x08,0x04,0x08,0x04,0xC8,0x7F,0x3F,0x04,0x08,0x04,0x08,0x04,0xA8,0x3F, 182 | // 0x18,0x21,0x0C,0x11,0x0B,0x12,0x08,0x0A,0x08,0x04,0x08,0x0A,0x8A,0x11,0x64,0x60,/*"技",6*/ 183 | 184 | 185 | 186 | // }; 187 | 188 | #endif 189 | 190 | 191 | -------------------------------------------------------------------------------- /Firmware/include/lcd/lcd.h: -------------------------------------------------------------------------------- 1 | #ifndef __LCD_H 2 | #define __LCD_H 3 | 4 | #include "systick.h" 5 | #include "stdlib.h" 6 | #include "gd32vf103_gpio.h" 7 | 8 | #define USE_HORIZONTAL 0 // rotation 0 through 3 9 | #define HAS_BLK_CNTL 0 10 | 11 | #if USE_HORIZONTAL==0||USE_HORIZONTAL==1 12 | #define LCD_W 80 13 | #define LCD_H 160 14 | #else 15 | #define LCD_W 160 16 | #define LCD_H 80 17 | #endif 18 | 19 | typedef unsigned char u8; 20 | typedef unsigned int u16; 21 | typedef unsigned long u32; 22 | 23 | 24 | // #define LED_ON gpio_bit_reset(GPIOC,GPIO_PIN_13) 25 | // #define LED_OFF gpio_bit_set(GPIOC,GPIO_PIN_13) 26 | 27 | #define LED_ON 28 | #define LED_OFF 29 | 30 | #define SPI0_CFG 1 //hardware spi 31 | // #define SPI0_CFG 2 //hardware spi dma - not working 32 | // #define SPI0_CFG 3 //software spi 33 | 34 | #define FRAME_SIZE 25600 // XXX why is this not 160*80 = 12800 ? 35 | 36 | //-----------------OLED端口定义---------------- 37 | #if SPI0_CFG == 1 38 | #define OLED_SCLK_Clr() 39 | #define OLED_SCLK_Set() 40 | 41 | #define OLED_SDIN_Clr() 42 | #define OLED_SDIN_Set() 43 | 44 | #define OLED_CS_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_2) //CS PB2 45 | #define OLED_CS_Set() gpio_bit_set(GPIOB,GPIO_PIN_2) 46 | #elif SPI0_CFG == 2 47 | #define OLED_SCLK_Clr() 48 | #define OLED_SCLK_Set() 49 | 50 | #define OLED_SDIN_Clr() 51 | #define OLED_SDIN_Set() 52 | 53 | #define OLED_CS_Clr() 54 | #define OLED_CS_Set() 55 | #else /* SPI0_CFG */ 56 | #define OLED_SCLK_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_5) //CLK PA5 57 | #define OLED_SCLK_Set() gpio_bit_set(GPIOA,GPIO_PIN_5) 58 | 59 | #define OLED_SDIN_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_7) //DIN PA7 60 | #define OLED_SDIN_Set() gpio_bit_set(GPIOA,GPIO_PIN_7) 61 | 62 | #define OLED_CS_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_2) //CS PB2 63 | #define OLED_CS_Set() gpio_bit_set(GPIOB,GPIO_PIN_2) 64 | #endif /* SPI0_CFG */ 65 | 66 | #define OLED_RST_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_1) //RES PB1 67 | #define OLED_RST_Set() gpio_bit_set(GPIOB,GPIO_PIN_1) 68 | 69 | #define OLED_DC_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_0) //DC PB0 70 | #define OLED_DC_Set() gpio_bit_set(GPIOB,GPIO_PIN_0) 71 | 72 | 73 | #if HAS_BLK_CNTL 74 | #define OLED_BLK_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_5)//BLK 75 | #define OLED_BLK_Set() gpio_bit_set(GPIOA,GPIO_PIN_5) 76 | #else 77 | #define OLED_BLK_Clr() 78 | #define OLED_BLK_Set() 79 | #endif 80 | 81 | #define OLED_CMD 0 //写命令 82 | #define OLED_DATA 1 //写数据 83 | 84 | extern u16 BACK_COLOR; //背景色 85 | extern unsigned char image[12800]; 86 | 87 | void LCD_Writ_Bus(u8 dat); 88 | void LCD_WR_DATA8(u8 dat); 89 | void LCD_WR_DATA(u16 dat); 90 | void LCD_WR_REG(u8 dat); 91 | void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2); 92 | void Lcd_Init(void); 93 | void LCD_Clear(u16 Color); 94 | void LCD_ShowChinese(u16 x,u16 y,u8 index,u8 size,u16 color); 95 | void LCD_DrawPoint(u16 x,u16 y,u16 color); 96 | void LCD_DrawPoint_big(u16 x,u16 y,u16 color); 97 | void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color); 98 | void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color); 99 | void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color); 100 | void Draw_Circle(u16 x0,u16 y0,u8 r,u16 color); 101 | void LCD_ShowChar(u16 x,u16 y,u8 num,u8 mode,u16 color); 102 | void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 color); 103 | u32 mypow(u8 m,u8 n); 104 | void LCD_ShowNum(u16 x,u16 y,u16 num,u8 len,u16 color); 105 | void LCD_ShowNum1(u16 x,u16 y,float num,u8 len,u16 color); 106 | void LCD_ShowPicture(u16 x1,u16 y1,u16 x2,u16 y2); 107 | void LCD_ShowLogo(void); 108 | 109 | 110 | //画笔颜色 111 | #define WHITE 0xFFFF 112 | #define BLACK 0x0000 113 | #define BLUE 0x001F 114 | #define BRED 0XF81F 115 | #define GRED 0XFFE0 116 | #define GBLUE 0X07FF 117 | #define RED 0xF800 118 | #define MAGENTA 0xF81F 119 | #define GREEN 0x07E0 120 | #define CYAN 0x7FFF 121 | #define YELLOW 0xFFE0 122 | #define BROWN 0XBC40 //棕色 123 | #define BRRED 0XFC07 //棕红色 124 | #define GRAY 0X8430 //灰色 125 | //GUI颜色 126 | 127 | #define DARKBLUE 0X01CF //深蓝色 128 | #define LIGHTBLUE 0X7D7C //浅蓝色 129 | #define GRAYBLUE 0X5458 //灰蓝色 130 | //以上三色为PANEL的颜色 131 | 132 | #define LIGHTGREEN 0X841F //浅绿色 133 | #define LGRAY 0XC618 //浅灰色(PANNEL),窗体背景色 134 | 135 | #define LGRAYBLUE 0XA651 //浅灰蓝色(中间层颜色) 136 | #define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色) 137 | 138 | 139 | 140 | #endif 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /Firmware/include/lcd/oledfont.h: -------------------------------------------------------------------------------- 1 | #ifndef __OLEDFONT_H 2 | #define __OLEDFONT_H 3 | //常用ASCII表 4 | //偏移量32 5 | //ASCII字符集 6 | //偏移量32 7 | //大小:12*6 8 | typedef unsigned char u8; 9 | typedef unsigned int u16; 10 | typedef unsigned long u32; 11 | 12 | /************************************6*8的点阵************************************/ 13 | 14 | 15 | /****************************************32*32的点阵************************************/ 16 | u8 Hzk32[]={ 17 | 18 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x01,0x00, 19 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 20 | 0x10,0x80,0x01,0x0C,0xF0,0xFF,0xFF,0x0F,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04, 21 | 0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04, 22 | 0x30,0x80,0x01,0x04,0x30,0x80,0x01,0x04,0xF0,0xFF,0xFF,0x07,0x30,0x80,0x01,0x04, 23 | 0x30,0x80,0x01,0x04,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 24 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 25 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,/*"中",0*/ 26 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01, 27 | 0x00,0xFF,0xFF,0x01,0x00,0x03,0x80,0x01,0x00,0x03,0x80,0x01,0x00,0xFF,0xFF,0x01, 28 | 0x00,0x03,0x80,0x01,0x00,0x03,0x80,0x01,0x00,0xFF,0xFF,0x01,0x00,0x81,0x81,0x00, 29 | 0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x1C,0xFC,0xFF,0xFF,0x3F,0x00,0x00,0x40,0x00, 30 | 0x00,0x03,0xC0,0x01,0x00,0xFF,0xFF,0x01,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00, 31 | 0x00,0x03,0xC0,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x41,0x00,0x00,0x04,0x01,0x00, 32 | 0x00,0x0E,0x71,0x00,0x00,0x07,0x81,0x01,0x80,0x01,0x01,0x07,0x60,0x00,0x01,0x0E, 33 | 0x18,0x98,0x01,0x1C,0x04,0xE0,0x01,0x18,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,/*"景",1*/ 34 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xF0,0xFF,0xFF,0x1F, 35 | 0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x10,0x0C,0x30,0xFE,0x3F,0x0C, 36 | 0x30,0x04,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C, 37 | 0x30,0x00,0xC0,0x0C,0xF0,0xFF,0xFF,0x0D,0x30,0x30,0x06,0x0E,0x30,0x30,0x06,0x0C, 38 | 0x30,0x10,0x06,0x0C,0x30,0x10,0x06,0x0C,0x30,0x10,0x06,0x0C,0x30,0x18,0x86,0x0C, 39 | 0x30,0x18,0x86,0x0C,0x30,0x08,0x06,0x0D,0x30,0x0C,0x86,0x0D,0x30,0x06,0xEE,0x0F, 40 | 0x30,0x02,0xFC,0x0D,0x30,0x01,0x00,0x0C,0xF0,0x00,0x00,0x0C,0x30,0x00,0x00,0x0C, 41 | 0xF0,0xFF,0xFF,0x0F,0x30,0x00,0x00,0x0C,0x10,0x00,0x00,0x04,0x00,0x00,0x00,0x00,/*"园",2*/ 42 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x01,0x00, 43 | 0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00, 44 | 0xE0,0xFF,0xFF,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03, 45 | 0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0xE0,0xFF,0xFF,0x03, 46 | 0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03,0x60,0xC0,0x00,0x03, 47 | 0x60,0xC0,0x00,0x03,0xE0,0xFF,0xFF,0x03,0x60,0xC0,0x00,0x01,0x60,0xC0,0x00,0x00, 48 | 0x00,0xC0,0x00,0x08,0x00,0xC0,0x00,0x10,0x00,0xC0,0x00,0x10,0x00,0xC0,0x00,0x18, 49 | 0x00,0xC0,0x01,0x38,0x00,0x80,0xFF,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"电",3*/ 50 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03, 51 | 0xC0,0xFF,0xFF,0x07,0x00,0x00,0x80,0x07,0x00,0x00,0xC0,0x00,0x00,0x00,0x60,0x00, 52 | 0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x80,0x03,0x00,0x00,0x80,0x03,0x00, 53 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x08,0x00,0x80,0x01,0x1C,0xFC,0xFF,0xFF,0x3F, 54 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 55 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 56 | 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00, 57 | 0x00,0xFC,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,/*"子",4*/}; 58 | 59 | const u8 asc2_1608[1520]={ 60 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 61 | 0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x18,0x18,0x00,0x00, 62 | 0x00,0x48,0x6C,0x24,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 63 | 0x00,0x00,0x00,0x24,0x24,0x24,0x7F,0x12,0x12,0x12,0x7F,0x12,0x12,0x12,0x00,0x00, 64 | 0x00,0x00,0x08,0x1C,0x2A,0x2A,0x0A,0x0C,0x18,0x28,0x28,0x2A,0x2A,0x1C,0x08,0x08, 65 | 0x00,0x00,0x00,0x22,0x25,0x15,0x15,0x15,0x2A,0x58,0x54,0x54,0x54,0x22,0x00,0x00, 66 | 0x00,0x00,0x00,0x0C,0x12,0x12,0x12,0x0A,0x76,0x25,0x29,0x11,0x91,0x6E,0x00,0x00, 67 | 0x00,0x06,0x06,0x04,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 68 | 0x00,0x40,0x20,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x10,0x10,0x20,0x40,0x00, 69 | 0x00,0x02,0x04,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x08,0x04,0x02,0x00, 70 | 0x00,0x00,0x00,0x00,0x08,0x08,0x6B,0x1C,0x1C,0x6B,0x08,0x08,0x00,0x00,0x00,0x00, 71 | 0x00,0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x7F,0x08,0x08,0x08,0x08,0x00,0x00,0x00, 72 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x04,0x03, 73 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 74 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x00,0x00, 75 | 0x00,0x00,0x80,0x40,0x40,0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,0x02,0x02,0x00, 76 | 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, 77 | 0x00,0x00,0x00,0x08,0x0E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 78 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x20,0x20,0x10,0x08,0x04,0x42,0x7E,0x00,0x00, 79 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x20,0x18,0x20,0x40,0x40,0x42,0x22,0x1C,0x00,0x00, 80 | 0x00,0x00,0x00,0x20,0x30,0x28,0x24,0x24,0x22,0x22,0x7E,0x20,0x20,0x78,0x00,0x00, 81 | 0x00,0x00,0x00,0x7E,0x02,0x02,0x02,0x1A,0x26,0x40,0x40,0x42,0x22,0x1C,0x00,0x00, 82 | 0x00,0x00,0x00,0x38,0x24,0x02,0x02,0x1A,0x26,0x42,0x42,0x42,0x24,0x18,0x00,0x00, 83 | 0x00,0x00,0x00,0x7E,0x22,0x22,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00, 84 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00, 85 | 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x64,0x58,0x40,0x40,0x24,0x1C,0x00,0x00, 86 | 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, 87 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x04, 88 | 0x00,0x00,0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x40,0x00,0x00, 89 | 0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00, 90 | 0x00,0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x02,0x00,0x00, 91 | 0x00,0x00,0x00,0x3C,0x42,0x42,0x46,0x40,0x20,0x10,0x10,0x00,0x18,0x18,0x00,0x00, 92 | 0x00,0x00,0x00,0x1C,0x22,0x5A,0x55,0x55,0x55,0x55,0x2D,0x42,0x22,0x1C,0x00,0x00, 93 | 0x00,0x00,0x00,0x08,0x08,0x18,0x14,0x14,0x24,0x3C,0x22,0x42,0x42,0xE7,0x00,0x00, 94 | 0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x1E,0x22,0x42,0x42,0x42,0x22,0x1F,0x00,0x00, 95 | 0x00,0x00,0x00,0x7C,0x42,0x42,0x01,0x01,0x01,0x01,0x01,0x42,0x22,0x1C,0x00,0x00, 96 | 0x00,0x00,0x00,0x1F,0x22,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x22,0x1F,0x00,0x00, 97 | 0x00,0x00,0x00,0x3F,0x42,0x12,0x12,0x1E,0x12,0x12,0x02,0x42,0x42,0x3F,0x00,0x00, 98 | 0x00,0x00,0x00,0x3F,0x42,0x12,0x12,0x1E,0x12,0x12,0x02,0x02,0x02,0x07,0x00,0x00, 99 | 0x00,0x00,0x00,0x3C,0x22,0x22,0x01,0x01,0x01,0x71,0x21,0x22,0x22,0x1C,0x00,0x00, 100 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 101 | 0x00,0x00,0x00,0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 102 | 0x00,0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x11,0x0F, 103 | 0x00,0x00,0x00,0x77,0x22,0x12,0x0A,0x0E,0x0A,0x12,0x12,0x22,0x22,0x77,0x00,0x00, 104 | 0x00,0x00,0x00,0x07,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x7F,0x00,0x00, 105 | 0x00,0x00,0x00,0x77,0x36,0x36,0x36,0x36,0x2A,0x2A,0x2A,0x2A,0x2A,0x6B,0x00,0x00, 106 | 0x00,0x00,0x00,0xE3,0x46,0x46,0x4A,0x4A,0x52,0x52,0x52,0x62,0x62,0x47,0x00,0x00, 107 | 0x00,0x00,0x00,0x1C,0x22,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x22,0x1C,0x00,0x00, 108 | 0x00,0x00,0x00,0x3F,0x42,0x42,0x42,0x42,0x3E,0x02,0x02,0x02,0x02,0x07,0x00,0x00, 109 | 0x00,0x00,0x00,0x1C,0x22,0x41,0x41,0x41,0x41,0x41,0x4D,0x53,0x32,0x1C,0x60,0x00, 110 | 0x00,0x00,0x00,0x3F,0x42,0x42,0x42,0x3E,0x12,0x12,0x22,0x22,0x42,0xC7,0x00,0x00, 111 | 0x00,0x00,0x00,0x7C,0x42,0x42,0x02,0x04,0x18,0x20,0x40,0x42,0x42,0x3E,0x00,0x00, 112 | 0x00,0x00,0x00,0x7F,0x49,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00, 113 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, 114 | 0x00,0x00,0x00,0xE7,0x42,0x42,0x22,0x24,0x24,0x14,0x14,0x18,0x08,0x08,0x00,0x00, 115 | 0x00,0x00,0x00,0x6B,0x49,0x49,0x49,0x49,0x55,0x55,0x36,0x22,0x22,0x22,0x00,0x00, 116 | 0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x18,0x18,0x18,0x24,0x24,0x42,0xE7,0x00,0x00, 117 | 0x00,0x00,0x00,0x77,0x22,0x22,0x14,0x14,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00, 118 | 0x00,0x00,0x00,0x7E,0x21,0x20,0x10,0x10,0x08,0x04,0x04,0x42,0x42,0x3F,0x00,0x00, 119 | 0x00,0x78,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x78,0x00, 120 | 0x00,0x00,0x02,0x02,0x04,0x04,0x08,0x08,0x08,0x10,0x10,0x20,0x20,0x20,0x40,0x40, 121 | 0x00,0x1E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x1E,0x00, 122 | 0x00,0x38,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 123 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF, 124 | 0x00,0x06,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 125 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x78,0x44,0x42,0x42,0xFC,0x00,0x00, 126 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x1A,0x26,0x42,0x42,0x42,0x26,0x1A,0x00,0x00, 127 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x44,0x02,0x02,0x02,0x44,0x38,0x00,0x00, 128 | 0x00,0x00,0x00,0x60,0x40,0x40,0x40,0x78,0x44,0x42,0x42,0x42,0x64,0xD8,0x00,0x00, 129 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x7E,0x02,0x02,0x42,0x3C,0x00,0x00, 130 | 0x00,0x00,0x00,0xF0,0x88,0x08,0x08,0x7E,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 131 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x22,0x22,0x1C,0x02,0x3C,0x42,0x42,0x3C, 132 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x3A,0x46,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 133 | 0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 134 | 0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x22,0x1E, 135 | 0x00,0x00,0x00,0x03,0x02,0x02,0x02,0x72,0x12,0x0A,0x16,0x12,0x22,0x77,0x00,0x00, 136 | 0x00,0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3E,0x00,0x00, 137 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x92,0x92,0x92,0x92,0x92,0xB7,0x00,0x00, 138 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x46,0x42,0x42,0x42,0x42,0xE7,0x00,0x00, 139 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, 140 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x26,0x42,0x42,0x42,0x22,0x1E,0x02,0x07, 141 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x44,0x42,0x42,0x42,0x44,0x78,0x40,0xE0, 142 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x4C,0x04,0x04,0x04,0x04,0x1F,0x00,0x00, 143 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x42,0x02,0x3C,0x40,0x42,0x3E,0x00,0x00, 144 | 0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x3E,0x08,0x08,0x08,0x08,0x08,0x30,0x00,0x00, 145 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x42,0x42,0x42,0x42,0x62,0xDC,0x00,0x00, 146 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x14,0x08,0x08,0x00,0x00, 147 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEB,0x49,0x49,0x55,0x55,0x22,0x22,0x00,0x00, 148 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x24,0x18,0x18,0x18,0x24,0x6E,0x00,0x00, 149 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x42,0x24,0x24,0x14,0x18,0x08,0x08,0x07, 150 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x22,0x10,0x08,0x08,0x44,0x7E,0x00,0x00, 151 | 0x00,0xC0,0x20,0x20,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x20,0x20,0xC0,0x00, 152 | 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 153 | 0x00,0x06,0x08,0x08,0x08,0x08,0x08,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x06,0x00, 154 | 0x0C,0x32,0xC2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 155 | }; 156 | 157 | 158 | 159 | 160 | u8 Hzk16[]={ 161 | 162 | 163 | 0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,0x84,0x10, 164 | 0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,/*"中",0*/ 165 | 166 | 0xF8,0x0F,0x08,0x08,0xF8,0x0F,0x08,0x08,0xF8,0x0F,0x80,0x00,0xFF,0x7F,0x00,0x00, 167 | 0xF8,0x0F,0x08,0x08,0x08,0x08,0xF8,0x0F,0x80,0x00,0x84,0x10,0xA2,0x20,0x40,0x00,/*"景",1*/ 168 | 169 | 0x00,0x00,0xFE,0x3F,0x02,0x20,0xF2,0x27,0x02,0x20,0x02,0x20,0xFA,0x2F,0x22,0x21, 170 | 0x22,0x21,0x22,0x21,0x12,0x29,0x12,0x29,0x0A,0x2E,0x02,0x20,0xFE,0x3F,0x02,0x20,/*"园",2*/ 171 | 172 | 0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F, 173 | 0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x50,0x80,0x40,0x80,0x40,0x00,0x7F,/*"电",3*/ 174 | 175 | 0x00,0x00,0xFE,0x1F,0x00,0x08,0x00,0x04,0x00,0x02,0x80,0x01,0x80,0x00,0xFF,0x7F, 176 | 0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xA0,0x00,0x40,0x00,/*"子",4*/ 177 | 178 | 0x10,0x08,0xB8,0x08,0x0F,0x09,0x08,0x09,0x08,0x08,0xBF,0x08,0x08,0x09,0x1C,0x09, 179 | 0x2C,0x08,0x0A,0x78,0xCA,0x0F,0x09,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,/*"科",5*/ 180 | 181 | 0x08,0x04,0x08,0x04,0x08,0x04,0xC8,0x7F,0x3F,0x04,0x08,0x04,0x08,0x04,0xA8,0x3F, 182 | 0x18,0x21,0x0C,0x11,0x0B,0x12,0x08,0x0A,0x08,0x04,0x08,0x0A,0x8A,0x11,0x64,0x60,/*"技",6*/ 183 | 184 | 185 | 186 | }; 187 | 188 | #endif 189 | 190 | 191 | -------------------------------------------------------------------------------- /Firmware/include/systick.h: -------------------------------------------------------------------------------- 1 | /*! 2 | \file systick.h 3 | \brief the header file of systick 4 | 5 | \version 2019-6-5, V1.0.0, firmware for GD32VF103 6 | */ 7 | 8 | /* 9 | Copyright (c) 2019, GigaDevice Semiconductor Inc. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 3. Neither the name of the copyright holder nor the names of its contributors 20 | may be used to endorse or promote products derived from this software without 21 | specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 32 | OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef SYS_TICK_H 36 | #define SYS_TICK_H 37 | 38 | #include 39 | 40 | void delay(uint32_t count); 41 | void delayMicros(uint32_t count); 42 | 43 | 44 | #endif /* SYS_TICK_H */ 45 | -------------------------------------------------------------------------------- /Firmware/lib/1AUDfilter/1AUDfilter.cpp: -------------------------------------------------------------------------------- 1 | /** ---------------------------------------------------------------------------------------- 2 | * 1AUD Filter 3 | * 4 | * Designed by Chris Thompson 5 | * 6 | * Implementation by James Kingdon 7 | * 8 | * The 1AUD filter is a dynamic filter that builds on the 1 Euro filter. 9 | * Compared to 1E the 1AUD uses stacked PT1 filters as input to the derivative, enabling 10 | * the use of higher derivative filter cut-off frequencies (and therefore faster tracking 11 | * of D), and stacked PT1 filters for the main filter, providing stronger (2nd order) 12 | * filtering of the input signal. Slew limiting of the input is also implemented. 13 | * 14 | * This software is released under the MIT license: 15 | * 16 | * Copyright 2020 C Thompson, J Kingdon 17 | * 18 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 19 | * software and associated documentation files (the "Software"), to deal in the Software 20 | * without restriction, including without limitation the rights to use, copy, modify, merge, 21 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit 22 | * persons to whom the Software is furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in all copies or 25 | * substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 28 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 29 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 30 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | * ---------------------------------------------------------------------------------------- 34 | */ 35 | 36 | // #include 37 | #include "1AUDfilter.h" 38 | 39 | // Some compilers mis-optimise the use of float constants and get better performance 40 | // with an explicit declaration 41 | static const float FASTZERO = 0.0f; 42 | static const float FAST2PI = 6.28318531f; 43 | 44 | DoublePT1filter::DoublePT1filter() 45 | { 46 | current1 = FASTZERO; 47 | current2 = FASTZERO; 48 | } 49 | 50 | float DoublePT1filter::update(const float x) 51 | { 52 | current1 = current1 + k * (x - current1); 53 | current2 = current2 + k * (current1 - current2); 54 | return current2; 55 | } 56 | 57 | float DoublePT1filter::getCurrent() 58 | { 59 | return current2; 60 | } 61 | 62 | float DoublePT1filter::getK() 63 | { 64 | return k; 65 | } 66 | 67 | void DoublePT1filter::setK(const float newK) 68 | { 69 | k = newK; 70 | } 71 | 72 | /** 73 | * Note that the cutoff frequencies must be less than sampleRate/(2Pi) in order for the filter 74 | * to remain stable 75 | */ 76 | OneAUDfilter::OneAUDfilter(const float _minFreq, const float _maxFreq, const float _beta, const float _sampleRate, 77 | const float _dCutoff, const float _maxSlew, const float initialValue) 78 | { 79 | // save the params 80 | minFreq = _minFreq; 81 | maxFreq = _maxFreq; 82 | beta = _beta; 83 | sampleRate = _sampleRate; 84 | maxSlewPerSecond = _maxSlew; 85 | dCutoff = _dCutoff; 86 | 87 | // calculate derived values 88 | kScale = FAST2PI / _sampleRate; 89 | maxSlewPerSample = _maxSlew / _sampleRate; 90 | slewDisabled = (_maxSlew == FASTZERO); 91 | 92 | // other setup 93 | prevInput = initialValue; 94 | prevSmoothed = FASTZERO; 95 | 96 | // set k for the derivative 97 | const float k = dCutoff * kScale; 98 | // Serial.printf("d k %f\n", k); 99 | dFilt.setK(k); 100 | 101 | // Set k for the output filter 102 | const float kOut = minFreq * kScale; 103 | // Serial.printf("kOut %f\n", kOut); 104 | oFilt.setK(kOut); 105 | } 106 | 107 | void OneAUDfilter::setSampleRate(const float newSampleRate) 108 | { 109 | sampleRate = newSampleRate; 110 | maxSlewPerSample = maxSlewPerSecond / sampleRate; 111 | 112 | // precompute 2 * PI / sampleRate and save for future use 113 | kScale = FAST2PI / sampleRate; 114 | 115 | float kD = dCutoff * kScale; 116 | // make sure kD is reasonable 117 | if (kD >= 1.0f) { 118 | kD = 0.99f; 119 | } 120 | dFilt.setK(kD); 121 | 122 | // The output PT1s get a new K at each update, so no need to set here 123 | } 124 | 125 | float OneAUDfilter::slewLimit(const float x) 126 | { 127 | // static float avSlew = 0; 128 | 129 | if (slewDisabled) { 130 | return x; 131 | } 132 | 133 | const float s = x - prevInput; 134 | 135 | // avSlew -= avSlew/10.0f; 136 | // if (s>0.0f) { 137 | // avSlew += s/10.0f; 138 | // } else { 139 | // avSlew += (-s)/10.0f; 140 | // } 141 | 142 | if (s > maxSlewPerSample) { 143 | // Serial.printf("%f > %f (av %f)\n", s, maxSlewPerSample, avSlew/10.0f); 144 | return prevInput + maxSlewPerSample; 145 | } else if ((-s) > maxSlewPerSample) { 146 | // Serial.printf("%f < -%f (av %f)\n", s, maxSlewPerSample, avSlew/10.0f); 147 | return prevInput - maxSlewPerSample; 148 | } 149 | 150 | return x; 151 | } 152 | 153 | // update the filter with a new value 154 | float OneAUDfilter::update(const float newValue) 155 | { 156 | // slew limit the input 157 | const float limitedNew = slewLimit(newValue); 158 | 159 | // apply the derivative filter to the input 160 | const float df = dFilt.update(limitedNew); 161 | 162 | // ## get differential of filtered input 163 | const float dx = (df - prevSmoothed) * sampleRate; 164 | const float absDx = dx >= FASTZERO ? dx : -dx; 165 | 166 | // update prevSmoothed 167 | prevSmoothed = df; 168 | 169 | // ## adjust cutoff upwards using dx and Beta 170 | float fMain = minFreq + (beta * absDx); 171 | if (fMain > maxFreq) { 172 | fMain = maxFreq; 173 | } 174 | 175 | // ## get the k value for the cutoff 176 | // kCutoff = 2*PI*cutoff/sampleRate. 177 | // 2*PI/sampleRate has been pre-computed and held in kScale 178 | const float kMain = fMain * kScale; 179 | oFilt.setK(kMain); 180 | 181 | // apply the main filter to the input 182 | const float mf = oFilt.update(limitedNew); 183 | 184 | // save the current value as an int so that we can use it from an ISR 185 | currentIntValue = mf; 186 | 187 | // update the previous value 188 | prevInput = newValue; 189 | 190 | return mf; 191 | } 192 | 193 | // get the current filtered value 194 | float OneAUDfilter::getCurrent() 195 | { 196 | return oFilt.getCurrent(); 197 | } 198 | 199 | float OneAUDfilter::getCutoff() 200 | { 201 | return oFilt.getK() / kScale; 202 | } 203 | 204 | 205 | /** Since we can't use floats in ISRs, this function 206 | * provides a way to get the current value as an int 207 | */ 208 | int32_t OneAUDfilter::getCurrentAsInt() 209 | { 210 | return currentIntValue; 211 | } 212 | -------------------------------------------------------------------------------- /Firmware/lib/1AUDfilter/1AUDfilter.h: -------------------------------------------------------------------------------- 1 | /** ---------------------------------------------------------------------------------------- 2 | * 1AUD Filter 3 | * 4 | * Designed by Chris Thompson 5 | * 6 | * Implementation by James Kingdon 7 | * 8 | * The 1AUD filter is a dynamic filter that builds on the 1 Euro filter. 9 | * Compared to 1E the 1AUD uses stacked PT1 filters as input to the derivative, enabling 10 | * the use of higher derivative filter cut-off frequencies (and therefore faster tracking 11 | * of D), and stacked PT1 filters for the main filter, providing stronger (2nd order) 12 | * filtering of the input signal. Slew limiting of the input is also implemented. 13 | * 14 | * This software is released under the MIT license: 15 | * 16 | * Copyright 2020 C Thompson, J Kingdon 17 | * 18 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 19 | * software and associated documentation files (the "Software"), to deal in the Software 20 | * without restriction, including without limitation the rights to use, copy, modify, merge, 21 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit 22 | * persons to whom the Software is furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in all copies or 25 | * substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 28 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 29 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 30 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | * ---------------------------------------------------------------------------------------- 34 | */ 35 | 36 | #pragma once 37 | 38 | #include 39 | 40 | #define ONEAUD_DEFAULT_DCUTOFF (50.0f) 41 | 42 | /** Two PT1 filters with a common k 43 | * Updates are processed by both PT1 sequentially, providing 2nd order filtering at the output 44 | */ 45 | class DoublePT1filter 46 | { 47 | private: 48 | float current1, current2, k; 49 | 50 | public: 51 | DoublePT1filter(); 52 | float update(const float x); 53 | float getK(); 54 | void setK(const float newK); 55 | float getCurrent(); 56 | }; 57 | 58 | class OneAUDfilter 59 | { 60 | private: 61 | 62 | float prevInput, prevSmoothed; 63 | float minFreq, maxFreq; 64 | float beta; 65 | float sampleRate; 66 | float kScale; // 2 * PI / sampleRate 67 | float dCutoff; 68 | float maxSlewPerSecond, maxSlewPerSample; 69 | bool slewDisabled; 70 | 71 | int32_t currentIntValue; 72 | 73 | // The filters for the derivative and output 74 | DoublePT1filter dFilt, oFilt; 75 | 76 | /** Limit the maximum per step delta in the input 77 | * This helps to remove the impact of measurement spikes in the data that 78 | * exceed what is physically possible in the domain space. The limit should be 79 | * higher than the noise threshold. 80 | */ 81 | float slewLimit(const float x); 82 | 83 | public: 84 | /** Constructor 85 | * @param minFreq cutoff frequency when the input is unchanging 86 | * @param maxFreq cutoff frequency when the input is rapidly changing 87 | * @param beta the rate at which the cutoff frequency is raised when the input is changing 88 | * @param sampleRate the frequency (Hz) at which the input data was sampled 89 | * @param dCutoffFreq the cutoff frequency for the derivative filter. Optional, default ONEAUD_DEFAULT_DCUTOFF Hz 90 | * @param maxSlew the maximum expected rate of change in steps per second. 0 disables slew limiting. Optional, default 0 91 | * @param initialValue a hint for the expected value to allow quicker initial settling at startup. Optional, default 0 92 | */ 93 | OneAUDfilter(const float minFreq, const float maxFreq, const float beta, const float sampleRate, 94 | const float dCutoffFreq = ONEAUD_DEFAULT_DCUTOFF, 95 | const float maxSlew = 0.0f, const float initialValue = 0.0f); 96 | 97 | // change the sample rate that the filter is running at 98 | void setSampleRate(const float newSampleRate); 99 | 100 | // update the filter with a new value 101 | float update(const float newValue); 102 | 103 | // get the current filtered value 104 | float getCurrent(); 105 | 106 | // get the current value of the dynamic cutoff frequency 107 | float getCutoff(); 108 | 109 | // get the current filtered value as an int32_t 110 | int32_t getCurrentAsInt(); 111 | 112 | }; 113 | -------------------------------------------------------------------------------- /Firmware/lib/1AUDfilter/1AUDfilterInt.cpp: -------------------------------------------------------------------------------- 1 | /** ---------------------------------------------------------------------------------------- 2 | * 1AUD Filter 3 | * 4 | * Designed by Chris Thompson 5 | * 6 | * Implementation by James Kingdon 7 | * 8 | * The 1AUD filter is a dynamic filter that builds on the 1 Euro filter. 9 | * Compared to 1E the 1AUD uses stacked PT1 filters as input to the derivative, enabling 10 | * the use of higher derivative filter cut-off frequencies (and therefore faster tracking 11 | * of D), and stacked PT1 filters for the main filter, providing stronger (2nd order) 12 | * filtering of the input signal. Slew limiting of the input is also implemented. 13 | * 14 | * This software is released under the MIT license: 15 | * 16 | * Copyright 2020 C Thompson, J Kingdon 17 | * 18 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 19 | * software and associated documentation files (the "Software"), to deal in the Software 20 | * without restriction, including without limitation the rights to use, copy, modify, merge, 21 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit 22 | * persons to whom the Software is furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in all copies or 25 | * substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 28 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 29 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 30 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | * ---------------------------------------------------------------------------------------- 34 | */ 35 | 36 | // #include 37 | #include "1AUDfilterInt.h" 38 | 39 | #include 40 | 41 | #define K_SHIFT_BITS 16 42 | // 2 * PI * 2^K_SHIFT_BITS for use when calculating k 43 | #define TWO_PI_SHIFTED (411775) 44 | 45 | #define PT_SCALE (128) 46 | 47 | DoublePT1filterInt::DoublePT1filterInt() 48 | { 49 | current1 = 0; 50 | current2 = 0; 51 | } 52 | 53 | void DoublePT1filterInt::setInitial(const int32_t x) 54 | { 55 | current1 = x; 56 | current2 = x; 57 | } 58 | 59 | int32_t DoublePT1filterInt::update(const int32_t x) 60 | { 61 | // factor of 2^K_SHIFT_BITS to compensate for resolution multiplier on the stored k 62 | current1 = current1 + ((k * (x - current1)) >> K_SHIFT_BITS); 63 | current2 = current2 + ((k * (current1 - current2)) >> K_SHIFT_BITS); 64 | return current2; 65 | } 66 | 67 | int32_t DoublePT1filterInt::getCurrent() 68 | { 69 | return current2; 70 | } 71 | 72 | void DoublePT1filterInt::setK(const uint32_t newK) 73 | { 74 | k = newK; 75 | } 76 | 77 | uint32_t DoublePT1filterInt::getK() 78 | { 79 | return k; 80 | } 81 | 82 | /** 83 | * Note that the cutoff frequencies must be less than sampleRate/(2Pi) in order for the filter 84 | * to remain stable 85 | */ 86 | OneAUDfilterInt::OneAUDfilterInt(const uint32_t _minFreq, const uint32_t _maxFreq, const uint32_t _inverseBeta, const uint32_t _sampleRate, 87 | const uint32_t _dCutoff, const uint32_t _maxSlew, const int32_t initialValue) 88 | { 89 | // save the params 90 | minFreq = _minFreq; 91 | maxFreq = _maxFreq; 92 | inverseBeta = _inverseBeta; 93 | sampleRate = _sampleRate; 94 | maxSlewPerSecond = _maxSlew; 95 | dCutoff = _dCutoff; 96 | 97 | // calculate derived values 98 | // kScale = FAST2PI / _sampleRate; kscale is just an optimisation, leave it out for now 99 | maxSlewPerSample = _maxSlew / _sampleRate; // we can probably live with integer slew per sample 100 | 101 | // other setup 102 | prevInput = initialValue; 103 | const uint32_t scaledIV = initialValue * PT_SCALE; 104 | dFilt.setInitial(scaledIV); 105 | oFilt.setInitial(scaledIV); 106 | prevSmoothed = scaledIV; 107 | 108 | // set k for the derivative (with a scaling factor to allow for storing in an int) 109 | const uint32_t k = dCutoff * TWO_PI_SHIFTED / sampleRate; 110 | 111 | // Serial.printf("d k %u\n", k); 112 | dFilt.setK(k); 113 | 114 | // Set k for the output filter 115 | const uint32_t kOut = minFreq * TWO_PI_SHIFTED / sampleRate; 116 | // Serial.printf("kOut %u\n", kOut); 117 | oFilt.setK(kOut); 118 | 119 | // TODO need to validate that maxFreq gives a stable value of k for the given sample rate 120 | } 121 | 122 | // reset the filter with new tunable parameters 123 | void OneAUDfilterInt::setNewFilterParams(const uint32_t _minFreq, const uint32_t _maxFreq, const uint32_t _inverseBeta) 124 | { 125 | // save the params 126 | minFreq = _minFreq; 127 | maxFreq = _maxFreq; 128 | inverseBeta = _inverseBeta; 129 | 130 | // Set k for the output filter 131 | const uint32_t kOut = minFreq * TWO_PI_SHIFTED / sampleRate; 132 | // Serial.printf("kOut %u\n", kOut); 133 | oFilt.setK(kOut); 134 | 135 | // TODO need to validate that maxFreq gives a stable value of k for the given sample rate 136 | } 137 | 138 | 139 | void OneAUDfilterInt::setSampleRate(const uint32_t newSampleRate) 140 | { 141 | sampleRate = newSampleRate; 142 | maxSlewPerSample = maxSlewPerSecond / sampleRate; 143 | 144 | uint32_t kD = dCutoff * TWO_PI_SHIFTED / sampleRate; 145 | // make sure kD is reasonable TODO figure this out again 146 | // if (kD >= 1000) { 147 | // kD = 990; 148 | // } 149 | dFilt.setK(kD); 150 | 151 | // The output PT1s get a new K at each update, so no need to set here 152 | } 153 | 154 | // calculate a modified value of x that respects the slew limit 155 | int32_t OneAUDfilterInt::slewLimit(const int32_t x) 156 | { 157 | // static uint32_t avAbsSlew = 0; 158 | 159 | // printf("max sps %lu\n", maxSlewPerSample); 160 | 161 | if (maxSlewPerSample == 0) { 162 | return x; 163 | } 164 | 165 | const int32_t s = x - prevInput; 166 | 167 | // avAbsSlew -= avAbsSlew/8; 168 | // if (s>0) { 169 | // avAbsSlew += s/8; 170 | // } else { 171 | // avAbsSlew += (-s)/8; 172 | // } 173 | 174 | if (s > (int32_t)maxSlewPerSample) { 175 | // Serial.printf("%d > %d (av %d)\n", s, maxSlewPerSample, avAbsSlew/8); 176 | return prevInput + maxSlewPerSample; 177 | } else if ((-s) > (int32_t)maxSlewPerSample) { 178 | // Serial.printf("%d < -%d (av %d)\n", s, maxSlewPerSample, avAbsSlew/8); 179 | return prevInput - maxSlewPerSample; 180 | } 181 | 182 | return x; 183 | } 184 | 185 | // update the filter with a new value 186 | // scaling is crucial to retain precision while avoiding overflow 187 | // TODO figure our the range of input values that don't overflow 188 | int32_t OneAUDfilterInt::update(const int32_t newValue) 189 | { 190 | // slew limit the input 191 | const int32_t limitedNew = slewLimit(newValue); 192 | 193 | // apply the derivative filter to the input 194 | const int32_t df = dFilt.update(limitedNew * PT_SCALE); 195 | 196 | // get differential of filtered input 197 | const int32_t dx = (df - prevSmoothed) * sampleRate; 198 | const int32_t absDx = dx >= 0 ? dx : -dx; 199 | 200 | // printf("dx, abdsDx %d %d\n", dx, absDx); 201 | 202 | // update prevSmoothed 203 | prevSmoothed = df; 204 | 205 | // ## adjust cutoff upwards using dx and Beta 206 | uint32_t fMain = (minFreq) + (absDx / (inverseBeta * PT_SCALE)); 207 | // Serial.printf("dx %d fMain %d ", absDx, fMain); 208 | if (fMain > (maxFreq)) { 209 | fMain = maxFreq; 210 | } 211 | 212 | // printf("%d\n", fMain/PT_SCALE); 213 | 214 | // ## get the k value for the cutoff 215 | // kCutoff = 2*PI*cutoff/sampleRate. 216 | const uint32_t kMain = fMain * TWO_PI_SHIFTED / sampleRate; 217 | oFilt.setK(kMain); 218 | 219 | // printf("kMain %u\n", kMain); 220 | 221 | // apply the main filter to the input 222 | const int32_t mf = oFilt.update(limitedNew * PT_SCALE); 223 | 224 | // update the previous value 225 | prevInput = limitedNew; // XXX originally newValue, but that allows the fixed point math to overflow at startup 226 | 227 | return mf / PT_SCALE; 228 | } 229 | 230 | // get the current filtered value 231 | int32_t OneAUDfilterInt::getCurrent() 232 | { 233 | return oFilt.getCurrent() / PT_SCALE; 234 | } 235 | 236 | float OneAUDfilterInt::getCutoff() 237 | { 238 | return (float(oFilt.getK()) * sampleRate) / TWO_PI_SHIFTED; 239 | } 240 | -------------------------------------------------------------------------------- /Firmware/lib/1AUDfilter/1AUDfilterInt.h: -------------------------------------------------------------------------------- 1 | /** ---------------------------------------------------------------------------------------- 2 | * 1AUD Filter 3 | * 4 | * Designed by Chris Thompson 5 | * 6 | * Implementation by James Kingdon 7 | * 8 | * The 1AUD filter is a dynamic filter that builds on the 1 Euro filter. 9 | * Compared to 1E the 1AUD uses stacked PT1 filters as input to the derivative, enabling 10 | * the use of higher derivative filter cut-off frequencies (and therefore faster tracking 11 | * of D), and stacked PT1 filters for the main filter, providing stronger (2nd order) 12 | * filtering of the input signal. Slew limiting of the input is also implemented. 13 | * 14 | * This software is released under the MIT license: 15 | * 16 | * Copyright 2020 C Thompson, J Kingdon 17 | * 18 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 19 | * software and associated documentation files (the "Software"), to deal in the Software 20 | * without restriction, including without limitation the rights to use, copy, modify, merge, 21 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit 22 | * persons to whom the Software is furnished to do so, subject to the following conditions: 23 | * 24 | * The above copyright notice and this permission notice shall be included in all copies or 25 | * substantial portions of the Software. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 28 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 29 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 30 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | * ---------------------------------------------------------------------------------------- 34 | */ 35 | 36 | #pragma once 37 | 38 | #include 39 | 40 | #define ONEAUD_DEFAULT_DCUTOFF (50) 41 | 42 | /** Two PT1 filters with a common k 43 | * Updates are processed by both PT1 sequentially, providing 2nd order filtering at the output 44 | */ 45 | class DoublePT1filterInt 46 | { 47 | private: 48 | int32_t current1, current2; 49 | // stored and used as k * 1000 50 | // XXX TODO make scaling a power of 2 51 | int32_t k; 52 | 53 | public: 54 | DoublePT1filterInt(); 55 | int32_t update(const int32_t x); 56 | uint32_t getK(); 57 | void setK(const uint32_t newK); 58 | int32_t getCurrent(); 59 | void setInitial(const int32_t x); 60 | }; 61 | 62 | class OneAUDfilterInt 63 | { 64 | private: 65 | 66 | int32_t prevInput, prevSmoothed; 67 | uint32_t minFreq, maxFreq; 68 | int32_t inverseBeta; // scaling? 69 | uint32_t sampleRate; 70 | uint32_t dCutoff; 71 | uint32_t maxSlewPerSecond, maxSlewPerSample; 72 | 73 | 74 | // The filters for the derivative and output 75 | DoublePT1filterInt dFilt, oFilt; 76 | 77 | /** Limit the maximum per step delta in the input 78 | * This helps to remove the impact of measurement spikes in the data that 79 | * exceed what is physically possible in the domain space. The limit should be 80 | * higher than the noise threshold. 81 | */ 82 | int32_t slewLimit(const int32_t x); 83 | 84 | public: 85 | /** Constructor 86 | * @param minFreq cutoff frequency when the input is unchanging 87 | * @param maxFreq cutoff frequency when the input is rapidly changing 88 | * @param inverseBeta the inverse rate at which the cutoff frequency is raised when the input is changing (1/beta used in float version) 89 | * @param sampleRate the frequency (Hz) at which the input data was sampled 90 | * @param dCutoffFreq the cutoff frequency for the derivative filter. Optional, default ONEAUD_DEFAULT_DCUTOFF Hz 91 | * @param maxSlew the maximum expected rate of change in steps per second. 0 disables slew limiting. Optional, default 0 92 | * @param initialValue a hint for the expected value to allow quicker initial settling at startup. Optional, default 0 93 | */ 94 | OneAUDfilterInt(const uint32_t minFreq, const uint32_t maxFreq, const uint32_t inverseBeta, const uint32_t sampleRate, 95 | const uint32_t dCutoffFreq = ONEAUD_DEFAULT_DCUTOFF, 96 | const uint32_t maxSlew = 0, const int32_t initialValue = 0); 97 | 98 | // Change the main tunable parameters of the filter 99 | void setNewFilterParams(const uint32_t minFreq, const uint32_t maxFreq, const uint32_t inverseBeta); 100 | 101 | // change the sample rate that the filter is running at 102 | void setSampleRate(const uint32_t newSampleRate); 103 | 104 | // update the filter with a new value 105 | int32_t update(const int32_t newValue); 106 | 107 | // get the current filtered value 108 | int32_t getCurrent(); 109 | 110 | float getCutoff(); 111 | 112 | }; 113 | -------------------------------------------------------------------------------- /Firmware/lib/CRC/crc.cpp: -------------------------------------------------------------------------------- 1 | #include "crc.h" 2 | 3 | #define ICACHE_RAM_ATTR 4 | 5 | GENERIC_CRC8::GENERIC_CRC8(uint8_t poly) 6 | { 7 | uint32_t i; 8 | uint8_t j; 9 | uint8_t crc; 10 | 11 | for (i = 0; i < crclen; i++) 12 | { 13 | crc = i; 14 | for (j = 0; j < 8; j++) 15 | { 16 | crc = (crc << 1) ^ ((crc & 0x80) ? poly : 0); 17 | } 18 | crc8tab[i] = crc & 0xFF; 19 | } 20 | } 21 | 22 | uint8_t ICACHE_RAM_ATTR GENERIC_CRC8::calc(uint8_t *data, uint8_t len) 23 | { 24 | uint8_t crc = 0; 25 | for (uint8_t i = 0; i < len; i++) 26 | { 27 | crc = crc8tab[crc ^ *data++]; 28 | } 29 | return crc; 30 | } 31 | 32 | uint8_t ICACHE_RAM_ATTR GENERIC_CRC8::calc(volatile uint8_t *data, uint8_t len) 33 | { 34 | uint8_t crc = 0; 35 | for (uint8_t i = 0; i < len; i++) 36 | { 37 | crc = crc8tab[crc ^ *data++]; 38 | } 39 | return crc; 40 | } 41 | -------------------------------------------------------------------------------- /Firmware/lib/CRC/crc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | // #include "../../src/targets.h" 4 | 5 | #define crclen 256 6 | 7 | class GENERIC_CRC8 8 | { 9 | private: 10 | uint8_t crc8tab[crclen]; 11 | uint8_t crcpoly; 12 | 13 | public: 14 | GENERIC_CRC8(uint8_t poly); 15 | uint8_t calc(uint8_t *data, uint8_t len); 16 | uint8_t calc(volatile uint8_t *data, uint8_t len); 17 | }; 18 | -------------------------------------------------------------------------------- /Firmware/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280.cpp: -------------------------------------------------------------------------------- 1 | // get access to gnu specific pow10 function 2 | #define _GNU_SOURCE 3 | 4 | #include "SX1280_Regs.h" 5 | #include "SX1280_hal.h" 6 | #include "SX1280.h" 7 | 8 | extern "C" { 9 | #include "../../include/systick.h" 10 | } 11 | #include "../../src/utils.h" 12 | #include "../../src/config.h" 13 | 14 | #include 15 | #include 16 | 17 | SX1280Hal hal; 18 | ///////////////////////////////////////////////////////////////// 19 | SX1280Driver *SX1280Driver::instance = NULL; 20 | //InterruptAssignment_ InterruptAssignment = NONE; 21 | 22 | //uint8_t SX127xDriver::_syncWord = SX127X_SYNC_WORD; 23 | 24 | //uint8_t SX127xDriver::currPWR = 0b0000; 25 | //uint8_t SX127xDriver::maxPWR = 0b1111; 26 | 27 | /* Steps for startup 28 | 29 | 1. If not in STDBY_RC mode, then go to this mode by sending the command: 30 | SetStandby(STDBY_RC) 31 | 32 | 2. Define the LoRa® packet type by sending the command: 33 | SetPacketType(PACKET_TYPE_LORA) 34 | 35 | 3. Define the RF frequency by sending the command: 36 | SetRfFrequency(rfFrequency) 37 | The LSB of rfFrequency is equal to the PLL step i.e. 52e6/2^18 Hz. SetRfFrequency() defines the Tx frequency. 38 | 39 | 4. Indicate the addresses where the packet handler will read (txBaseAddress in Tx) or write (rxBaseAddress in Rx) the first 40 | byte of the data payload by sending the command: 41 | SetBufferBaseAddress(txBaseAddress, rxBaseAddress) 42 | Note: 43 | txBaseAddress and rxBaseAddress are offset relative to the beginning of the data memory map. 44 | 45 | 5. Define the modulation parameter signal BW SF CR 46 | */ 47 | 48 | 49 | 50 | uint32_t beginTX; 51 | uint32_t endTX; 52 | 53 | void ICACHE_RAM_ATTR SX1280Driver::nullCallback(void){return;}; 54 | 55 | SX1280Driver::SX1280Driver() 56 | { 57 | instance = this; 58 | } 59 | 60 | void SX1280Driver::End() 61 | { 62 | hal.end(); 63 | instance->TXdoneCallback = &nullCallback; // remove callbacks 64 | instance->RXdoneCallback = &nullCallback; 65 | } 66 | 67 | // flrc specific setup 68 | void SX1280Driver::setupFLRC() 69 | { 70 | this->SetMode(SX1280_MODE_STDBY_RC); //step 1 put in STDBY_RC mode 71 | hal.WriteCommand(SX1280_RADIO_SET_PACKETTYPE, SX1280_PACKET_TYPE_FLRC); //Step 2: set packet type 72 | // this->ConfigModParamsFLRC(FLRC_BR_0_325_BW_0_3, FLRC_CR_1_2, BT_DIS); //Step 5: Configure Modulation Params 73 | this->ConfigModParamsFLRC(FLRC_BR_1_300_BW_1_2, FLRC_CR_1_2, BT_DIS); //Step 5: Configure Modulation Params 74 | hal.WriteCommand(SX1280_RADIO_SET_AUTOFS, 0x01); //enable auto FS 75 | 76 | // setpacketparams for flrc mode 77 | SetPacketParamsFLRC(); 78 | 79 | // setup the syncword - currently have it disabled in void SetPacketParamsFLRC, anything needed here? 80 | } 81 | 82 | // lora specific setup 83 | void SX1280Driver::setupLora() 84 | { 85 | this->SetMode(SX1280_MODE_STDBY_RC); //step 1 put in STDBY_RC mode 86 | hal.WriteCommand(SX1280_RADIO_SET_PACKETTYPE, SX1280_PACKET_TYPE_LORA); //Step 2: set packet type to LoRa 87 | this->ConfigModParams(currBW, currSF, currCR); //Step 5: Configure Modulation Params 88 | hal.WriteCommand(SX1280_RADIO_SET_AUTOFS, 0x01); //enable auto FS 89 | #ifdef USE_HARDWARE_CRC 90 | this->SetPacketParams(12, SX1280_LORA_PACKET_IMPLICIT, 8, SX1280_LORA_CRC_ON, SX1280_LORA_IQ_NORMAL); 91 | #else 92 | this->SetPacketParams(12, SX1280_LORA_PACKET_IMPLICIT, 8, SX1280_LORA_CRC_OFF, SX1280_LORA_IQ_NORMAL); 93 | #endif 94 | } 95 | 96 | void SX1280Driver::Begin() 97 | { 98 | hal.init(); 99 | // hal.TXdoneCallback = &SX1280Driver::TXnbISR; 100 | // hal.RXdoneCallback = &SX1280Driver::RXnbISR; 101 | 102 | printf("reset\n\r"); 103 | hal.reset(); 104 | 105 | // expected value is 43447 (A9B7) (TODO add list of other good values as we see them) 106 | uint16_t firmwareRev = (((hal.ReadRegister(REG_LR_FIRMWARE_VERSION_MSB)) << 8) | (hal.ReadRegister(REG_LR_FIRMWARE_VERSION_MSB + 1))); 107 | printf("Firmware Revision: %u (%X)\n\r", firmwareRev, firmwareRev); 108 | if (firmwareRev != 0xA9B7) { 109 | printf("WARNING: firmware not the expected value of 0xA9B7\n\r"); 110 | } 111 | 112 | #ifdef USE_FLRC 113 | setupFLRC(); 114 | #else 115 | setupLora(); 116 | #endif // USE_FLRC 117 | 118 | this->SetFrequency(this->currFreq); //Step 3: Set Freq 119 | this->SetFIFOaddr(0x00, 0x00); //Step 4: Config FIFO addr 120 | 121 | // Using dual dios for rx and tx done 122 | this->SetDioIrqParams(SX1280_IRQ_RADIO_ALL, SX1280_IRQ_RX_DONE, SX1280_IRQ_TX_DONE, SX1280_IRQ_RADIO_NONE); 123 | } 124 | 125 | 126 | void ICACHE_RAM_ATTR SX1280Driver::ConfigFLRC(uint32_t freq) 127 | { 128 | this->setupFLRC(); 129 | SetFrequency(freq); 130 | } 131 | 132 | 133 | void ICACHE_RAM_ATTR SX1280Driver::Config(SX1280_RadioLoRaBandwidths_t bw, SX1280_RadioLoRaSpreadingFactors_t sf, 134 | SX1280_RadioLoRaCodingRates_t cr, uint32_t freq, uint8_t PreambleLength) 135 | { 136 | this->SetMode(SX1280_MODE_STDBY_XOSC); 137 | ConfigModParams(bw, sf, cr); 138 | #ifdef USE_HARDWARE_CRC 139 | SetPacketParams(PreambleLength, SX1280_LORA_PACKET_IMPLICIT, 8, SX1280_LORA_CRC_ON, SX1280_LORA_IQ_NORMAL); // TODO don't make static etc. 140 | #else 141 | SetPacketParams(PreambleLength, SX1280_LORA_PACKET_IMPLICIT, 8, SX1280_LORA_CRC_OFF, SX1280_LORA_IQ_NORMAL); // TODO don't make static etc. 142 | #endif 143 | 144 | SetFrequency(freq); 145 | } 146 | 147 | /** convert prePA power to mW 148 | * @param power the prePA power as used by currPWR 149 | */ 150 | uint16_t SX1280Driver::convertPowerToMw(int power) 151 | { 152 | // convert from dBm to mW 153 | #if defined(RADIO_E28_27) 154 | // for e28-27, PA output is +27dBm of the pre-PA setting, up to a max of 0 input. 155 | uint16_t mw = pow10(float(power+27)/10.0f); 156 | #elif defined(RADIO_E28_20) 157 | // for e28-20, PA output is +22dBm of the pre-PA setting, up to a max of -2 input. 158 | uint16_t mw = pow10(float(power+22)/10.0f); 159 | #elif defined(RADIO_E28_12) 160 | // for e28-12 output is just the current setting 161 | uint16_t mw = pow10(float(power)/10.0f) + 0.5; // round to nearest 162 | #else 163 | #error("must define a radio module") 164 | #endif 165 | 166 | return mw; 167 | } 168 | 169 | // TODO - make subclasses of sx1280 that provide implementations of this method for each radio module 170 | // TODO = should this return a float for low power modules? 171 | uint16_t ICACHE_RAM_ATTR SX1280Driver::getPowerMw() 172 | { 173 | return convertPowerToMw(currPWR); 174 | } 175 | 176 | /** Set the SX1280 power output 177 | * @param power The output level of the sx1280 chip itself (pre any PA) in dBm, 178 | * range -18 to +13 for non-PA modules. The max power will be capped by MAX_PRE_PA_POWER 179 | * which must be correctly set for the module being used. 180 | */ 181 | void ICACHE_RAM_ATTR SX1280Driver::SetOutputPower(int8_t power) 182 | { 183 | #ifndef MAX_PRE_PA_POWER 184 | #error "Must set MAX_PRE_PA_POWER for sx1280 modules" 185 | #endif 186 | 187 | if (power > MAX_PRE_PA_POWER) { 188 | printf("power capped for E28\n"); 189 | power = MAX_PRE_PA_POWER; 190 | } else if (power < -18) { 191 | printf("power min limit\n"); 192 | power = -18; 193 | } 194 | 195 | uint8_t buf[2]; 196 | buf[0] = power + 18; 197 | buf[1] = (uint8_t)SX1280_RADIO_RAMP_04_US; 198 | hal.WriteCommand(SX1280_RADIO_SET_TXPARAMS, buf, 2); 199 | 200 | currPWR = power; 201 | 202 | // Serial.print("SetPower raw: "); 203 | // Serial.println(buf[0]); 204 | return; 205 | } 206 | 207 | 208 | void SX1280Driver::SetPacketParams(uint8_t PreambleLength, SX1280_RadioLoRaPacketLengthsModes_t HeaderType, uint8_t PayloadLength, 209 | SX1280_RadioLoRaCrcModes_t crc, SX1280_RadioLoRaIQModes_t InvertIQ) 210 | { 211 | uint8_t buf[8]; 212 | 213 | buf[0] = SX1280_RADIO_SET_PACKETPARAMS; 214 | buf[1] = PreambleLength; 215 | buf[2] = HeaderType; 216 | buf[3] = PayloadLength; 217 | buf[4] = crc; 218 | buf[5] = InvertIQ; 219 | buf[6] = 0x00; 220 | buf[7] = 0x00; 221 | 222 | hal.fastWriteCommand(buf, sizeof(buf)); 223 | } 224 | 225 | /** 226 | * packetParam1 = AGCPreambleLength 227 | • packetParam2 = SyncWordLength 228 | • packetParam3 = SyncWordMatch 229 | • packetParam4 = PacketType 230 | • packetParam5 = PayloadLength 231 | • packetParam6 = CrcLength 232 | • packetParam7 = Whitening 233 | */ 234 | void SX1280Driver::SetPacketParamsFLRC() 235 | { 236 | uint8_t buf[8]; 237 | 238 | buf[0] = SX1280_RADIO_SET_PACKETPARAMS; 239 | buf[1] = 0x30; // PREAMBLE_LENGTH_16_BITS 0x30 240 | buf[2] = 0x00; // SyncWordLength FLRC_SYNC_NOSYNC 0x00 241 | buf[3] = 0x00; // SyncWordMatch RX_DISABLE_SYNC_WORD 0x00 242 | buf[4] = 0x00; // PacketType PACKET_FIXED_LENGTH 0x00 243 | buf[5] = 8; // PayloadLength 244 | buf[6] = 0x10; // CrcLength Docs are contradictory, this may be 2 bytes: CRC_1_BYTE 0x10 245 | buf[7] = 0x08; // 0x08 Whitening must be disabled for FLRC 246 | 247 | hal.fastWriteCommand(buf, sizeof(buf)); 248 | } 249 | 250 | 251 | void SX1280Driver::SetMode(SX1280_RadioOperatingModes_t OPmode) 252 | { 253 | 254 | //if (OPmode == currOpmode) 255 | //{ 256 | // return; 257 | //} 258 | 259 | uint8_t buf3[3]; //TODO make word alignmed 260 | 261 | switch (OPmode) 262 | { 263 | 264 | case SX1280_MODE_SLEEP: 265 | hal.WriteCommand(SX1280_RADIO_SET_SLEEP, 0x01); 266 | break; 267 | 268 | case SX1280_MODE_CALIBRATION: 269 | break; 270 | 271 | case SX1280_MODE_STDBY_RC: 272 | hal.WriteCommand(SX1280_RADIO_SET_STANDBY, SX1280_STDBY_RC); 273 | break; 274 | case SX1280_MODE_STDBY_XOSC: 275 | hal.WriteCommand(SX1280_RADIO_SET_STANDBY, SX1280_STDBY_XOSC); 276 | break; 277 | 278 | case SX1280_MODE_FS: 279 | hal.WriteCommand(SX1280_RADIO_SET_FS, 0x00); 280 | break; 281 | 282 | case SX1280_MODE_RX: 283 | 284 | buf3[0] = 0x00; // periodBase = 1ms, page 71 datasheet, set to FF for cont RX 285 | buf3[1] = 0xFF; 286 | buf3[2] = 0xFF; 287 | hal.WriteCommand(SX1280_RADIO_SET_RX, buf3, sizeof(buf3)); 288 | break; 289 | 290 | case SX1280_MODE_TX: 291 | //uses timeout Time-out duration = periodBase * periodBaseCount 292 | buf3[0] = 0x00; // periodBase = 1ms, page 71 datasheet 293 | buf3[1] = 0xFF; // no timeout set for now 294 | buf3[2] = 0xFF; // TODO dynamic timeout based on expected onairtime 295 | hal.WriteCommand(SX1280_RADIO_SET_TX, buf3, sizeof(buf3)); 296 | break; 297 | 298 | case SX1280_MODE_CAD: 299 | break; 300 | 301 | default: 302 | break; 303 | } 304 | 305 | currOpmode = OPmode; 306 | } 307 | 308 | /** default is low power mode, switch to high sensitivity instead 309 | * */ 310 | void setHighSensitivity() 311 | { 312 | hal.WriteRegister(0x0891, (hal.ReadRegister(0x0891) | 0xC0)); 313 | } 314 | 315 | // XXX generalise to handle flrc or copy and specialise? 316 | 317 | void SX1280Driver::ConfigModParams(SX1280_RadioLoRaBandwidths_t bw, SX1280_RadioLoRaSpreadingFactors_t sf, SX1280_RadioLoRaCodingRates_t cr) 318 | { 319 | // Care must therefore be taken to ensure that modulation parameters are set using the command 320 | // SetModulationParam() only after defining the packet type SetPacketType() to be used 321 | 322 | uint8_t rfparams[3]; //TODO make word alignmed 323 | 324 | rfparams[0] = (uint8_t)sf; 325 | rfparams[1] = (uint8_t)bw; 326 | rfparams[2] = (uint8_t)cr; 327 | 328 | hal.WriteCommand(SX1280_RADIO_SET_MODULATIONPARAMS, rfparams, sizeof(rfparams)); 329 | 330 | /** 331 | * If the Spreading Factor selected is SF5 or SF6, it is required to use WriteRegister( 0x925, 0x1E ) 332 | • If the Spreading Factor is SF7 or SF-8 then the command WriteRegister( 0x925, 0x37 ) must be used 333 | • If the Spreading Factor is SF9, SF10, SF11 or SF12, then the command WriteRegister( 0x925, 0x32 ) must be used 334 | */ 335 | switch (sf) { 336 | case SX1280_RadioLoRaSpreadingFactors_t::SX1280_LORA_SF5: 337 | case SX1280_RadioLoRaSpreadingFactors_t::SX1280_LORA_SF6: 338 | hal.WriteRegister(0x925, 0x1E); // for SF5 or SF6 339 | break; 340 | case SX1280_RadioLoRaSpreadingFactors_t::SX1280_LORA_SF7: 341 | case SX1280_RadioLoRaSpreadingFactors_t::SX1280_LORA_SF8: 342 | hal.WriteRegister(0x925, 0x37); // for SF7 or SF8 343 | break; 344 | default: 345 | hal.WriteRegister(0x925, 0x32); // for SF9 and above 346 | } 347 | 348 | setHighSensitivity(); 349 | } 350 | 351 | void SX1280Driver::ConfigModParamsFLRC(SX1280_RadioFLRCBandwidths_t bw, SX1280_RadioFLRCCodingRates_t cr, SX1280_RadioFLRCBTFilter_t bt) 352 | { 353 | // Care must therefore be taken to ensure that modulation parameters are set using the command 354 | // SetModulationParam() only after defining the packet type SetPacketType() to be used 355 | 356 | uint8_t rfparams[3]; //TODO make word aligned 357 | 358 | rfparams[0] = (uint8_t)bw; 359 | rfparams[1] = (uint8_t)cr; 360 | rfparams[2] = (uint8_t)bt; 361 | 362 | hal.WriteCommand(SX1280_RADIO_SET_MODULATIONPARAMS, rfparams, sizeof(rfparams)); 363 | 364 | setHighSensitivity(); 365 | } 366 | 367 | 368 | void SX1280Driver::SetFrequency(uint32_t Reqfreq) 369 | { 370 | //Serial.println(Reqfreq); 371 | uint8_t buf[3]; //TODO make word alignmed 372 | 373 | uint32_t freq = (uint32_t)((double)Reqfreq / (double)SX1280_FREQ_STEP); 374 | buf[0] = (uint8_t)((freq >> 16) & 0xFF); 375 | buf[1] = (uint8_t)((freq >> 8) & 0xFF); 376 | buf[2] = (uint8_t)(freq & 0xFF); 377 | 378 | hal.WriteCommand(SX1280_RADIO_SET_RFFREQUENCY, buf, 3); 379 | currFreq = Reqfreq; 380 | } 381 | 382 | int32_t SX1280Driver::GetFrequencyError() 383 | { 384 | uint8_t efeRaw[3] = {0}; //TODO make word alignmed 385 | uint32_t efe = 0; 386 | double efeHz = 0.0; 387 | 388 | efeRaw[0] = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB); 389 | efeRaw[1] = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 1); 390 | efeRaw[2] = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 2); 391 | efe = (efeRaw[0] << 16) | (efeRaw[1] << 8) | efeRaw[2]; 392 | 393 | efe &= SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MASK; 394 | 395 | printf("GetFrequencyError IMPL NEEDED\n"); 396 | 397 | //efeHz = 1.55 * (double)complement2(efe, 20) / (1600.0 / (double)GetLoRaBandwidth() * 1000.0); XXX wuuuuut? 398 | return efeHz; 399 | } 400 | 401 | void SX1280Driver::SetFIFOaddr(uint8_t txBaseAddr, uint8_t rxBaseAddr) 402 | { 403 | uint8_t buf[2]; 404 | 405 | buf[0] = txBaseAddr; 406 | buf[1] = rxBaseAddr; 407 | hal.WriteCommand(SX1280_RADIO_SET_BUFFERBASEADDRESS, buf, 2); 408 | } 409 | 410 | // TODO use bigger buffer and a fastWrite 411 | void SX1280Driver::SetDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask) 412 | { 413 | uint8_t buf[8]; 414 | 415 | buf[0] = (uint8_t)((irqMask >> 8) & 0x00FF); 416 | buf[1] = (uint8_t)(irqMask & 0x00FF); 417 | buf[2] = (uint8_t)((dio1Mask >> 8) & 0x00FF); 418 | buf[3] = (uint8_t)(dio1Mask & 0x00FF); 419 | buf[4] = (uint8_t)((dio2Mask >> 8) & 0x00FF); 420 | buf[5] = (uint8_t)(dio2Mask & 0x00FF); 421 | buf[6] = (uint8_t)((dio3Mask >> 8) & 0x00FF); 422 | buf[7] = (uint8_t)(dio3Mask & 0x00FF); 423 | 424 | hal.WriteCommand(SX1280_RADIO_SET_DIOIRQPARAMS, buf, 8); 425 | } 426 | 427 | void SX1280Driver::ClearIrqStatus(uint16_t irqMask) 428 | { 429 | uint8_t buf[2]; 430 | 431 | buf[0] = (uint8_t)(((uint16_t)irqMask >> 8) & 0x00FF); 432 | buf[1] = (uint8_t)((uint16_t)irqMask & 0x00FF); 433 | 434 | hal.WriteCommand(SX1280_RADIO_CLR_IRQSTATUS, buf, 2); 435 | } 436 | 437 | // void SX1280Driver::TXnbISR() 438 | // { 439 | // //endTX = micros(); 440 | // instance->ClearIrqStatus(SX1280_IRQ_RADIO_ALL); 441 | // instance->currOpmode = SX1280_MODE_FS; // radio goes to FS 442 | // //Serial.print("TOA: "); 443 | // //Serial.println(endTX - beginTX); 444 | // //instance->GetStatus(); 445 | 446 | // // Serial.println("TXnbISR!"); 447 | // //instance->GetStatus(); 448 | 449 | // //instance->GetStatus(); 450 | // instance->TXdoneCallback(); 451 | // } 452 | 453 | // TODO - how does the syncword get set on TX? 454 | 455 | void SX1280Driver::TXnb(volatile uint8_t *data, uint8_t length) 456 | { 457 | instance->ClearIrqStatus(SX1280_IRQ_RADIO_ALL); 458 | hal.TXenable(); // do first to allow PA stablise 459 | instance->SetFIFOaddr(0x00, 0x00); // not 100% sure if needed again 460 | hal.WriteBuffer(0x00, data, length); //todo fix offset to equal fifo addr 461 | instance->SetMode(SX1280_MODE_TX); 462 | // beginTX = micros(); 463 | } 464 | 465 | // void SX1280Driver::RXnbISR() 466 | // { 467 | // instance->currOpmode = SX1280_MODE_FS; // XXX is this true? Unless we're doing single rx with timeout, we'll still be in rx mode 468 | 469 | // // Need to check for hardware crc error 470 | // #ifdef USE_HARDWARE_CRC 471 | // // grab the status before we clear it 472 | // uint16_t irqStat = GetIrqStatus(); 473 | // instance->ClearIrqStatus(SX1280_IRQ_RADIO_ALL); 474 | // if (irqStat & 0b1000000) { 475 | // // Serial.println("bad hw crc"); 476 | // return; 477 | // } 478 | // #else 479 | // instance->ClearIrqStatus(SX1280_IRQ_RADIO_ALL); 480 | // #endif 481 | 482 | // uint8_t FIFOaddr = instance->GetRxBufferAddr(); 483 | // hal.ReadBuffer(FIFOaddr, instance->RXdataBuffer, 8); 484 | // instance->RXdoneCallback(); 485 | // } 486 | 487 | void SX1280Driver::readRXData() 488 | { 489 | uint8_t FIFOaddr = instance->GetRxBufferAddr(); 490 | hal.ReadBuffer(FIFOaddr, instance->RXdataBuffer, 8); 491 | } 492 | 493 | void SX1280Driver::RXnb() 494 | { 495 | //Serial.println("Start RX nb"); 496 | hal.RXenable(); 497 | instance->ClearIrqStatus(SX1280_IRQ_RADIO_ALL); 498 | //instance->SetFIFOaddr(0x00, 0x00); 499 | instance->SetMode(SX1280_MODE_RX); 500 | } 501 | 502 | uint8_t ICACHE_RAM_ATTR SX1280Driver::GetRxBufferAddr() 503 | { 504 | uint8_t status[2]; 505 | hal.ReadCommand(SX1280_RADIO_GET_RXBUFFERSTATUS, status, 2); 506 | return status[1]; 507 | } 508 | 509 | void ICACHE_RAM_ATTR SX1280Driver::GetStatus() 510 | { 511 | uint8_t status = 0; 512 | 513 | uint8_t stat1; 514 | uint8_t stat2; 515 | bool busy; 516 | 517 | hal.ReadCommand(SX1280_RADIO_GET_STATUS, (uint8_t *)&status, 1); 518 | stat1 = (0b11100000 & status) >> 5; 519 | stat2 = (0b00011100 & status) >> 2; 520 | busy = 0b00000001 & status; 521 | printf("Status: %u, %u, %u (%X)\n", stat1, stat2, busy, status); 522 | } 523 | 524 | bool ICACHE_RAM_ATTR SX1280Driver::GetFrequencyErrorbool() 525 | { 526 | printf("GetFrequencyErrorbool IMPL NEEDED\n"); 527 | //uint8_t val = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB); 528 | //uint8_t val1 = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 1); 529 | //uint8_t val2 = hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 2); 530 | // uint8_t regEFI[3]; 531 | 532 | // hal.ReadRegister(SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB, regEFI, 3); 533 | 534 | //Serial.println(val); 535 | //Serial.println(val1); 536 | //Serial.println(val2); 537 | 538 | // Serial.println(regEFI[0]); 539 | // Serial.println(regEFI[1]); 540 | // Serial.println(regEFI[2]); 541 | 542 | //bool result = (val & 0b00001000) >> 3; 543 | //return result; // returns true if pos freq error, neg if false 544 | return 0; 545 | } 546 | 547 | void ICACHE_RAM_ATTR SX1280Driver::SetPPMoffsetReg(int32_t offset) { return; }; 548 | 549 | 550 | // TODO get rssi/snr can be made more efficient by using a single call to get 551 | // both values. 552 | int8_t ICACHE_RAM_ATTR SX1280Driver::GetLastPacketRSSI() 553 | { 554 | uint8_t status[2]; 555 | 556 | hal.ReadCommand(SX1280_RADIO_GET_PACKETSTATUS, status, 2); 557 | LastPacketRSSI = -(int8_t)(status[0]/2); 558 | // Serial.print("rssi read "); Serial.println(LastPacketRSSI); 559 | 560 | // grab snr while we have the buffer 561 | LastPacketSNR = (int8_t)(status[1]/4); 562 | 563 | return LastPacketRSSI; 564 | } 565 | 566 | int8_t ICACHE_RAM_ATTR SX1280Driver::GetLastPacketSNR() 567 | { 568 | uint8_t status[2]; 569 | 570 | hal.ReadCommand(SX1280_RADIO_GET_PACKETSTATUS, status, 2); 571 | LastPacketSNR = ((int8_t)status[1])/4; 572 | 573 | // grab rssi while we have the buffer 574 | LastPacketRSSI = -(int8_t)(status[0]/2); 575 | 576 | return LastPacketSNR; 577 | } 578 | 579 | uint16_t ICACHE_RAM_ATTR SX1280Driver::GetIrqStatus() 580 | { 581 | uint8_t status[2]; 582 | 583 | hal.ReadCommand(SX1280_RADIO_GET_IRQSTATUS, status, 2); 584 | 585 | return (((uint16_t)status[0]) << 8) + status[1]; 586 | } 587 | -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // #include "../../src/targets.h" 4 | #include "SX1280_Regs.h" 5 | #include "SX1280_hal.h" 6 | 7 | #include 8 | 9 | #define ICACHE_RAM_ATTR 10 | 11 | void ICACHE_RAM_ATTR TXnbISR(); 12 | 13 | enum InterruptAssignment_ 14 | { 15 | NONE, 16 | RX_DONE, 17 | TX_DONE 18 | }; 19 | 20 | class SX1280Driver 21 | { 22 | private: 23 | void setupLora(); 24 | void setupFLRC(); 25 | void SetPacketParamsFLRC(); 26 | 27 | public: 28 | ///////Callback Function Pointers///// 29 | static void ICACHE_RAM_ATTR nullCallback(void); 30 | 31 | void (*RXdoneCallback)() = &nullCallback; //function pointer for callback 32 | void (*TXdoneCallback)() = &nullCallback; //function pointer for callback 33 | 34 | static void (*TXtimeout)(); //function pointer for callback 35 | static void (*RXtimeout)(); //function pointer for callback 36 | 37 | InterruptAssignment_ InterruptAssignment = NONE; 38 | ///////////////////////////// 39 | 40 | ///////////Radio Variables//////// 41 | volatile uint8_t TXdataBuffer[256]; 42 | volatile uint8_t RXdataBuffer[256]; 43 | 44 | uint8_t TXbuffLen; 45 | uint8_t RXbuffLen; 46 | 47 | static uint8_t _syncWord; 48 | 49 | SX1280_RadioLoRaBandwidths_t currBW = SX1280_LORA_BW_0800; 50 | SX1280_RadioLoRaSpreadingFactors_t currSF = SX1280_LORA_SF6; 51 | SX1280_RadioLoRaCodingRates_t currCR = SX1280_LORA_CR_4_7; 52 | uint32_t currFreq = 2400000000; 53 | SX1280_RadioOperatingModes_t currOpmode = SX1280_MODE_SLEEP; 54 | 55 | int8_t currPWR; 56 | // static uint8_t maxPWR; 57 | 58 | /////////////////////////////////// 59 | 60 | /////////////Packet Stats////////// 61 | int8_t LastPacketRSSI = 0; 62 | int8_t LastPacketSNR = 0; 63 | volatile uint8_t NonceTX = 0; 64 | volatile uint8_t NonceRX = 0; 65 | static uint32_t TotalTime; 66 | static uint32_t TimeOnAir; 67 | static uint32_t TXstartMicros; 68 | static uint32_t TXspiTime; 69 | static uint32_t HeadRoom; 70 | static uint32_t TXdoneMicros; 71 | ///////////////////////////////// 72 | 73 | //// Local Variables //// Copy of values for SPI speed optimisation 74 | static uint8_t CURR_REG_PAYLOAD_LENGTH; 75 | static uint8_t CURR_REG_DIO_MAPPING_1; 76 | static uint8_t CURR_REG_FIFO_ADDR_PTR; 77 | 78 | ////////////////Configuration Functions///////////// 79 | SX1280Driver(); 80 | static SX1280Driver *instance; 81 | void Begin(); 82 | void End(); 83 | void SetMode(SX1280_RadioOperatingModes_t OPmode); 84 | void Config(SX1280_RadioLoRaBandwidths_t bw, SX1280_RadioLoRaSpreadingFactors_t sf, SX1280_RadioLoRaCodingRates_t cr, uint32_t freq, uint8_t PreambleLength); 85 | void ICACHE_RAM_ATTR ConfigFLRC(uint32_t freq); 86 | 87 | void ConfigModParams(SX1280_RadioLoRaBandwidths_t bw, SX1280_RadioLoRaSpreadingFactors_t sf, SX1280_RadioLoRaCodingRates_t cr); 88 | void ConfigModParamsFLRC(SX1280_RadioFLRCBandwidths_t bw, SX1280_RadioFLRCCodingRates_t cr, SX1280_RadioFLRCBTFilter_t bt); 89 | void SetPacketParams(uint8_t PreambleLength, SX1280_RadioLoRaPacketLengthsModes_t HeaderType, uint8_t PayloadLength, SX1280_RadioLoRaCrcModes_t crc, SX1280_RadioLoRaIQModes_t InvertIQ); 90 | void ICACHE_RAM_ATTR SetFrequency(uint32_t freq); 91 | void ICACHE_RAM_ATTR SetFIFOaddr(uint8_t txBaseAddr, uint8_t rxBaseAddr); 92 | void SetOutputPower(int8_t power); 93 | 94 | uint16_t convertPowerToMw(int power); 95 | uint16_t getPowerMw(); 96 | 97 | int32_t ICACHE_RAM_ATTR GetFrequencyError(); 98 | 99 | static void ICACHE_RAM_ATTR TXnb(volatile uint8_t *data, uint8_t length); 100 | static void ICACHE_RAM_ATTR TXnbISR(); //ISR for non-blocking TX routine 101 | 102 | static void ICACHE_RAM_ATTR RXnb(); 103 | static void ICACHE_RAM_ATTR RXnbISR(); //ISR for non-blocking RC routine 104 | 105 | void readRXData(); 106 | 107 | void ICACHE_RAM_ATTR ClearIrqStatus(uint16_t irqMask); 108 | 109 | void ICACHE_RAM_ATTR GetStatus(); 110 | 111 | void SetDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask); 112 | 113 | // uint8_t SetBandwidth(SX1280_RadioLoRaBandwidths_t bw); 114 | // uint32_t getCurrBandwidth(); 115 | // uint8_t SetOutputPower(uint8_t Power); 116 | // uint8_t SetPreambleLength(uint8_t PreambleLen); 117 | // uint8_t SetSpreadingFactor(SX1280_RadioLoRaSpreadingFactors_t sf); 118 | // uint8_t SetCodingRate(SX1280_RadioLoRaCodingRates_t cr); 119 | 120 | bool ICACHE_RAM_ATTR GetFrequencyErrorbool(); 121 | void ICACHE_RAM_ATTR SetPPMoffsetReg(int32_t offset); 122 | uint8_t ICACHE_RAM_ATTR GetRxBufferAddr(); 123 | 124 | int8_t ICACHE_RAM_ATTR GetLastPacketRSSI(); 125 | int8_t ICACHE_RAM_ATTR GetLastPacketSNR(); 126 | static uint16_t ICACHE_RAM_ATTR GetIrqStatus(); 127 | }; -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280Driver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SX1280_Regs.h" 3 | #include "SX1280_hal.h" 4 | #include "SX1280.h" 5 | // #include "../../src/targets.h" -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280_Regs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define REG_LR_FIRMWARE_VERSION_MSB 0x0153 //The address of the register holding the firmware version MSB 4 | #define SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB 0x0954 5 | #define SX1280_REG_LR_ESTIMATED_FREQUENCY_ERROR_MASK 0x0FFFFF 6 | 7 | #define SX1280_XTAL_FREQ 52000000 8 | #define SX1280_FREQ_STEP ((double)(SX1280_XTAL_FREQ / pow(2.0, 18.0))) 9 | 10 | typedef enum 11 | { 12 | SX1280_RF_IDLE = 0x00, //!< The radio is idle 13 | SX1280_RF_RX_RUNNING, //!< The radio is in reception state 14 | SX1280_RF_TX_RUNNING, //!< The radio is in transmission state 15 | SX1280_RF_CAD, //!< The radio is doing channel activity detection 16 | } SX1280_RadioStates_t; 17 | 18 | /*! 19 | * \brief Represents the operating mode the radio is actually running 20 | */ 21 | typedef enum 22 | { 23 | SX1280_MODE_SLEEP = 0x00, //! The radio is in sleep mode 24 | SX1280_MODE_CALIBRATION, //! The radio is in calibration mode 25 | SX1280_MODE_STDBY_RC, //! The radio is in standby mode with RC oscillator 26 | SX1280_MODE_STDBY_XOSC, //! The radio is in standby mode with XOSC oscillator 27 | SX1280_MODE_FS, //! The radio is in frequency synthesis mode 28 | SX1280_MODE_RX, //! The radio is in receive mode 29 | SX1280_MODE_TX, //! The radio is in transmit mode 30 | SX1280_MODE_CAD //! The radio is in channel activity detection mode 31 | } SX1280_RadioOperatingModes_t; 32 | 33 | #define SX1280_RX_TX_CONTINUOUS \ 34 | (TickTime_t) { RADIO_TICK_SIZE_0015_US, 0xFFFF } 35 | #define SX1280_RX_TX_SINGLE \ 36 | (TickTime_t) { RADIO_TICK_SIZE_0015_US, 0 } 37 | 38 | /*! 39 | * \brief Declares the oscillator in use while in standby mode 40 | * 41 | * Using the STDBY_RC standby mode allow to reduce the energy consumption 42 | * STDBY_XOSC should be used for time critical applications 43 | */ 44 | typedef enum 45 | { 46 | SX1280_STDBY_RC = 0x00, 47 | SX1280_STDBY_XOSC = 0x01, 48 | } SX1280_RadioStandbyModes_t; 49 | 50 | /*! 51 | * \brief Declares the power regulation used to power the device 52 | * 53 | * This command allows the user to specify if DC-DC or LDO is used for power regulation. 54 | * Using only LDO implies that the Rx or Tx current is doubled 55 | */ 56 | typedef enum 57 | { 58 | SX1280_USE_LDO = 0x00, //! Use LDO (default value) 59 | SX1280_USE_DCDC = 0x01, //! Use DCDC 60 | } SX1280_RadioRegulatorModes_t; 61 | 62 | /*! 63 | * \brief Represents the possible packet type (i.e. modem) used 64 | */ 65 | typedef enum 66 | { 67 | SX1280_PACKET_TYPE_GFSK = 0x00, 68 | SX1280_PACKET_TYPE_LORA, 69 | SX1280_PACKET_TYPE_RANGING, 70 | SX1280_PACKET_TYPE_FLRC, 71 | SX1280_PACKET_TYPE_BLE, 72 | SX1280_PACKET_TYPE_NONE = 0x0F, 73 | } SX1280_RadioPacketTypes_t; 74 | 75 | //only for GFSK 76 | // typedef enum 77 | // { 78 | // SX1280_PREAMBLE_LENGTH_04_BITS = 0x00, //!< Preamble length: 04 bits 79 | // SX1280_PREAMBLE_LENGTH_08_BITS = 0x10, //!< Preamble length: 08 bits 80 | // SX1280_PREAMBLE_LENGTH_12_BITS = 0x20, //!< Preamble length: 12 bits 81 | // SX1280_PREAMBLE_LENGTH_16_BITS = 0x30, //!< Preamble length: 16 bits 82 | // SX1280_PREAMBLE_LENGTH_20_BITS = 0x40, //!< Preamble length: 20 bits 83 | // SX1280_PREAMBLE_LENGTH_24_BITS = 0x50, //!< Preamble length: 24 bits 84 | // SX1280_PREAMBLE_LENGTH_28_BITS = 0x60, //!< Preamble length: 28 bits 85 | // SX1280_PREAMBLE_LENGTH_32_BITS = 0x70, //!< Preamble length: 32 bits 86 | // } SX1280_RadioPreambleLengths_t; 87 | 88 | typedef enum 89 | { 90 | SX1280_LORA_IQ_NORMAL = 0x40, 91 | SX1280_LORA_IQ_INVERTED = 0x00, 92 | } SX1280_RadioLoRaIQModes_t; 93 | 94 | typedef enum 95 | { 96 | SX1280_RADIO_CRC_OFF = 0x00, //!< No CRC in use 97 | SX1280_RADIO_CRC_1_BYTES = 0x10, 98 | SX1280_RADIO_CRC_2_BYTES = 0x20, 99 | SX1280_RADIO_CRC_3_BYTES = 0x30, 100 | } SX1280_RadioCrcTypes_t; 101 | 102 | /*! 103 | * \brief Represents the ramping time for power amplifier 104 | */ 105 | typedef enum 106 | { 107 | SX1280_RADIO_RAMP_02_US = 0x00, 108 | SX1280_RADIO_RAMP_04_US = 0x20, 109 | SX1280_RADIO_RAMP_06_US = 0x40, 110 | SX1280_RADIO_RAMP_08_US = 0x60, 111 | SX1280_RADIO_RAMP_10_US = 0x80, 112 | SX1280_RADIO_RAMP_12_US = 0xA0, 113 | SX1280_RADIO_RAMP_16_US = 0xC0, 114 | SX1280_RADIO_RAMP_20_US = 0xE0, 115 | } SX1280_RadioRampTimes_t; 116 | 117 | /*! 118 | * \brief Represents the number of symbols to be used for channel activity detection operation 119 | */ 120 | typedef enum 121 | { 122 | SX1280_LORA_CAD_01_SYMBOL = 0x00, 123 | SX1280_LORA_CAD_02_SYMBOLS = 0x20, 124 | SX1280_LORA_CAD_04_SYMBOLS = 0x40, 125 | SX1280_LORA_CAD_08_SYMBOLS = 0x60, 126 | SX1280_LORA_CAD_16_SYMBOLS = 0x80, 127 | } SX1280_RadioLoRaCadSymbols_t; 128 | 129 | /*! 130 | * \brief Represents the possible spreading factor values in LORA packet types 131 | */ 132 | typedef enum 133 | { 134 | SX1280_LORA_SF5 = 0x50, 135 | SX1280_LORA_SF6 = 0x60, 136 | SX1280_LORA_SF7 = 0x70, 137 | SX1280_LORA_SF8 = 0x80, 138 | SX1280_LORA_SF9 = 0x90, 139 | SX1280_LORA_SF10 = 0xA0, 140 | SX1280_LORA_SF11 = 0xB0, 141 | SX1280_LORA_SF12 = 0xC0, 142 | } SX1280_RadioLoRaSpreadingFactors_t; 143 | 144 | /*! 145 | * \brief Represents the bandwidth values for the LORA packet type 146 | */ 147 | typedef enum 148 | { 149 | SX1280_LORA_BW_0200 = 0x34, 150 | SX1280_LORA_BW_0400 = 0x26, 151 | SX1280_LORA_BW_0800 = 0x18, 152 | SX1280_LORA_BW_1600 = 0x0A, 153 | } SX1280_RadioLoRaBandwidths_t; 154 | 155 | typedef enum 156 | { 157 | FLRC_BR_0_260_BW_0_3 = 0xEB, 158 | FLRC_BR_0_325_BW_0_3 = 0xC7, 159 | FLRC_BR_0_650_BW_0_6 = 0x86, 160 | FLRC_BR_1_300_BW_1_2 = 0x45 161 | } SX1280_RadioFLRCBandwidths_t; 162 | 163 | /*! 164 | * \brief Represents the coding rate values for the LORA packet type 165 | */ 166 | typedef enum 167 | { 168 | SX1280_LORA_CR_4_5 = 0x01, 169 | SX1280_LORA_CR_4_6 = 0x02, 170 | SX1280_LORA_CR_4_7 = 0x03, 171 | SX1280_LORA_CR_4_8 = 0x04, 172 | SX1280_LORA_CR_LI_4_5 = 0x05, 173 | SX1280_LORA_CR_LI_4_6 = 0x06, 174 | SX1280_LORA_CR_LI_4_7 = 0x07, 175 | } SX1280_RadioLoRaCodingRates_t; 176 | 177 | typedef enum 178 | { 179 | FLRC_CR_1_2 = 0x00 180 | 181 | } SX1280_RadioFLRCCodingRates_t; 182 | 183 | // FLRC bluetooth filter setting 184 | typedef enum 185 | { 186 | BT_DIS = 0x00 187 | } SX1280_RadioFLRCBTFilter_t; 188 | 189 | 190 | typedef enum 191 | { 192 | SX1280_LORA_PACKET_VARIABLE_LENGTH = 0x00, //!< The packet is on variable size, header included 193 | SX1280_LORA_PACKET_FIXED_LENGTH = 0x80, //!< The packet is known on both sides, no header included in the packet 194 | SX1280_LORA_PACKET_EXPLICIT = SX1280_LORA_PACKET_VARIABLE_LENGTH, 195 | SX1280_LORA_PACKET_IMPLICIT = SX1280_LORA_PACKET_FIXED_LENGTH, 196 | } SX1280_RadioLoRaPacketLengthsModes_t; 197 | 198 | typedef enum 199 | { 200 | SX1280_LORA_CRC_ON = 0x20, //!< CRC activated 201 | SX1280_LORA_CRC_OFF = 0x00, //!< CRC not used 202 | } SX1280_RadioLoRaCrcModes_t; 203 | 204 | typedef enum RadioCommands_u 205 | { 206 | SX1280_RADIO_GET_STATUS = 0xC0, 207 | SX1280_RADIO_WRITE_REGISTER = 0x18, 208 | SX1280_RADIO_READ_REGISTER = 0x19, 209 | SX1280_RADIO_WRITE_BUFFER = 0x1A, 210 | SX1280_RADIO_READ_BUFFER = 0x1B, 211 | SX1280_RADIO_SET_SLEEP = 0x84, 212 | SX1280_RADIO_SET_STANDBY = 0x80, 213 | SX1280_RADIO_SET_FS = 0xC1, 214 | SX1280_RADIO_SET_TX = 0x83, 215 | SX1280_RADIO_SET_RX = 0x82, 216 | SX1280_RADIO_SET_RXDUTYCYCLE = 0x94, 217 | SX1280_RADIO_SET_CAD = 0xC5, 218 | SX1280_RADIO_SET_TXCONTINUOUSWAVE = 0xD1, 219 | SX1280_RADIO_SET_TXCONTINUOUSPREAMBLE = 0xD2, 220 | SX1280_RADIO_SET_PACKETTYPE = 0x8A, 221 | SX1280_RADIO_GET_PACKETTYPE = 0x03, 222 | SX1280_RADIO_SET_RFFREQUENCY = 0x86, 223 | SX1280_RADIO_SET_TXPARAMS = 0x8E, 224 | SX1280_RADIO_SET_CADPARAMS = 0x88, 225 | SX1280_RADIO_SET_BUFFERBASEADDRESS = 0x8F, 226 | SX1280_RADIO_SET_MODULATIONPARAMS = 0x8B, 227 | SX1280_RADIO_SET_PACKETPARAMS = 0x8C, 228 | SX1280_RADIO_GET_RXBUFFERSTATUS = 0x17, 229 | SX1280_RADIO_GET_PACKETSTATUS = 0x1D, 230 | SX1280_RADIO_GET_RSSIINST = 0x1F, 231 | SX1280_RADIO_SET_DIOIRQPARAMS = 0x8D, 232 | SX1280_RADIO_GET_IRQSTATUS = 0x15, 233 | SX1280_RADIO_CLR_IRQSTATUS = 0x97, 234 | SX1280_RADIO_CALIBRATE = 0x89, 235 | SX1280_RADIO_SET_REGULATORMODE = 0x96, 236 | SX1280_RADIO_SET_SAVECONTEXT = 0xD5, 237 | SX1280_RADIO_SET_AUTOTX = 0x98, 238 | SX1280_RADIO_SET_AUTOFS = 0x9E, 239 | SX1280_RADIO_SET_LONGPREAMBLE = 0x9B, 240 | SX1280_RADIO_SET_UARTSPEED = 0x9D, 241 | SX1280_RADIO_SET_RANGING_ROLE = 0xA3, 242 | } SX1280_RadioCommands_t; 243 | 244 | typedef enum 245 | { 246 | SX1280_IRQ_RADIO_NONE = 0x0000, 247 | SX1280_IRQ_TX_DONE = 0x0001, 248 | SX1280_IRQ_RX_DONE = 0x0002, 249 | SX1280_IRQ_SYNCWORD_VALID = 0x0004, 250 | SX1280_IRQ_SYNCWORD_ERROR = 0x0008, 251 | SX1280_IRQ_HEADER_VALID = 0x0010, 252 | SX1280_IRQ_HEADER_ERROR = 0x0020, 253 | SX1280_IRQ_CRC_ERROR = 0x0040, 254 | SX1280_IRQ_RANGING_SLAVE_RESPONSE_DONE = 0x0080, 255 | SX1280_IRQ_RANGING_SLAVE_REQUEST_DISCARDED = 0x0100, 256 | SX1280_IRQ_RANGING_MASTER_RESULT_VALID = 0x0200, 257 | SX1280_IRQ_RANGING_MASTER_TIMEOUT = 0x0400, 258 | SX1280_IRQ_RANGING_SLAVE_REQUEST_VALID = 0x0800, 259 | SX1280_IRQ_CAD_DONE = 0x1000, 260 | SX1280_IRQ_CAD_DETECTED = 0x2000, 261 | SX1280_IRQ_RX_TX_TIMEOUT = 0x4000, 262 | SX1280_IRQ_PREAMBLE_DETECTED = 0x8000, 263 | SX1280_IRQ_RADIO_ALL = 0xFFFF, 264 | } SX1280_RadioIrqMasks_t; 265 | 266 | typedef enum 267 | { 268 | SX1280_RADIO_DIO1 = 0x02, 269 | SX1280_RADIO_DIO2 = 0x04, 270 | SX1280_RADIO_DIO3 = 0x08, 271 | } SX1280_RadioDios_t; 272 | 273 | typedef enum 274 | { 275 | SX1280_RADIO_TICK_SIZE_0015_US = 0x00, 276 | SX1280_RADIO_TICK_SIZE_0062_US = 0x01, 277 | SX1280_RADIO_TICK_SIZE_1000_US = 0x02, 278 | SX1280_RADIO_TICK_SIZE_4000_US = 0x03, 279 | } SX1280_RadioTickSizes_t; -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280_hal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ______ _ 3 | / _____) _ | | 4 | ( (____ _____ ____ _| |_ _____ ____| |__ 5 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 6 | _____) ) ____| | | || |_| ____( (___| | | | 7 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 8 | (C)2016 Semtech 9 | 10 | Description: Handling of the node configuration protocol 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | 14 | Maintainer: Miguel Luis, Gregory Cristian and Matthieu Verdy 15 | 16 | Modified and adapted by Alessandro Carcione for ELRS project 17 | */ 18 | 19 | #include "../../src/config.h" 20 | #include "SX1280_Regs.h" 21 | #include "SX1280_hal.h" 22 | #include 23 | // #include 24 | #include "../../src/utils.h" 25 | #include // for memcpy 26 | 27 | extern "C" { 28 | #include "../../include/systick.h" 29 | } 30 | 31 | SX1280Hal *SX1280Hal::instance = nullptr; 32 | 33 | // void SX1280Hal::nullCallback(void){}; 34 | 35 | // void (*SX1280Hal::TXdoneCallback)() = &nullCallback; 36 | // void (*SX1280Hal::RXdoneCallback)() = &nullCallback; 37 | 38 | SX1280Hal::SX1280Hal() 39 | { 40 | instance = this; 41 | } 42 | 43 | void SX1280Hal::end() 44 | { 45 | // XXX todo 46 | // SPI.end(); 47 | // detachInterrupt(GPIO_PIN_DIO1); 48 | } 49 | 50 | void SX1280Hal::init() 51 | { 52 | // all pin/spi setup done in main.cpp 53 | } 54 | 55 | void SX1280Hal::reset(void) 56 | { 57 | gpio_bit_set(RADIO_RESET_PORT, RADIO_RESET_PIN); 58 | delay(50); 59 | gpio_bit_reset(RADIO_RESET_PORT, RADIO_RESET_PIN); 60 | delay(100); 61 | gpio_bit_set(RADIO_RESET_PORT, RADIO_RESET_PIN); 62 | delay(50); 63 | 64 | if (WaitOnBusy()) { 65 | printf("WARNING SX1280 busy didn't go low\n\r"); 66 | } else { 67 | printf("SX1280 Ready!\n\r"); 68 | } 69 | } 70 | 71 | void SX1280Hal::WriteCommand(const SX1280_RadioCommands_t command, const uint8_t val) 72 | { 73 | uint8_t buffer[2] = {command, val}; 74 | 75 | WaitOnBusy(); 76 | 77 | spi1_transferBytes(buffer, 2); 78 | } 79 | 80 | // TODO add a fastWrite command that just takes a buffer and size 81 | void SX1280Hal::WriteCommand(SX1280_RadioCommands_t command, uint8_t *buffer, uint8_t size) 82 | { 83 | uint8_t OutBuffer[size + 1]; 84 | 85 | OutBuffer[0] = (uint8_t)command; 86 | memcpy(OutBuffer + 1, buffer, size); 87 | 88 | WaitOnBusy(); 89 | 90 | spi1_transferBytes(OutBuffer, size+1); 91 | } 92 | 93 | /** faster version of Writecommand. 94 | * The command is passed in the first byte of buffer 95 | * size includes the command 96 | * contents of buffer will be overwritten 97 | */ 98 | void SX1280Hal::fastWriteCommand(uint8_t *buffer, uint8_t size) 99 | { 100 | WaitOnBusy(); 101 | 102 | spi1_transferBytes(buffer, size); 103 | } 104 | 105 | 106 | // TODO add fast read without the memory copying 107 | void SX1280Hal::ReadCommand(SX1280_RadioCommands_t command, uint8_t *buffer, uint8_t size) 108 | { 109 | uint8_t OutBuffer[size + 2]; 110 | 111 | WaitOnBusy(); 112 | 113 | if (command == SX1280_RADIO_GET_STATUS) 114 | { 115 | OutBuffer[0] = (uint8_t)command; 116 | OutBuffer[1] = 0x00; 117 | OutBuffer[2] = 0x00; 118 | spi1_transferBytes(OutBuffer, 3); 119 | buffer[0] = OutBuffer[0]; 120 | } 121 | else 122 | { 123 | OutBuffer[0] = (uint8_t)command; 124 | OutBuffer[1] = 0x00; 125 | memcpy(OutBuffer + 2, buffer, size); 126 | spi1_transferBytes(OutBuffer, sizeof(OutBuffer)); 127 | memcpy(buffer, OutBuffer + 2, size); 128 | } 129 | } 130 | 131 | void SX1280Hal::WriteRegister(uint16_t address, uint8_t *buffer, uint8_t size) 132 | { 133 | uint8_t OutBuffer[size + 3]; 134 | 135 | OutBuffer[0] = SX1280_RADIO_WRITE_REGISTER; 136 | OutBuffer[1] = address >> 8; 137 | OutBuffer[2] = address & 0x00FF; 138 | 139 | memcpy(OutBuffer + 3, buffer, size); 140 | 141 | WaitOnBusy(); 142 | // digitalWrite(GPIO_PIN_NSS, LOW); 143 | spi1_transferBytes(OutBuffer, (uint8_t)sizeof(OutBuffer)); 144 | // digitalWrite(GPIO_PIN_NSS, HIGH); 145 | } 146 | 147 | void SX1280Hal::WriteRegister(uint16_t address, uint8_t value) 148 | { 149 | WriteRegister(address, &value, 1); 150 | } 151 | 152 | void SX1280Hal::ReadRegister(uint16_t address, uint8_t *buffer, uint8_t size) 153 | { 154 | uint8_t OutBuffer[size + 4]; 155 | 156 | OutBuffer[0] = SX1280_RADIO_READ_REGISTER; 157 | OutBuffer[1] = address >> 8; 158 | OutBuffer[2] = address & 0x00FF; 159 | OutBuffer[3] = 0x00; 160 | 161 | memcpy(OutBuffer + 4, buffer, size); 162 | 163 | WaitOnBusy(); 164 | // digitalWrite(GPIO_PIN_NSS, LOW); 165 | 166 | spi1_transferBytes(OutBuffer, uint8_t(sizeof(OutBuffer))); 167 | memcpy(buffer, OutBuffer + 4, size); 168 | 169 | // digitalWrite(GPIO_PIN_NSS, HIGH); 170 | } 171 | 172 | uint8_t SX1280Hal::ReadRegister(uint16_t address) 173 | { 174 | uint8_t data=0; 175 | ReadRegister(address, &data, 1); 176 | return data; 177 | } 178 | 179 | void SX1280Hal::WriteBuffer(uint8_t offset, volatile uint8_t *buffer, uint8_t size) 180 | { 181 | // uint8_t localbuf[size]; 182 | 183 | // for (int i = 0; i < size; i++) // todo check if this is the right want to handle volatiles 184 | // { 185 | // localbuf[i] = buffer[i]; 186 | // } 187 | 188 | uint8_t OutBuffer[size + 2]; 189 | 190 | OutBuffer[0] = SX1280_RADIO_WRITE_BUFFER; 191 | OutBuffer[1] = offset; 192 | 193 | memcpy(OutBuffer + 2, (void *)buffer, size); 194 | 195 | WaitOnBusy(); 196 | 197 | // digitalWrite(GPIO_PIN_NSS, LOW); 198 | spi1_transferBytes(OutBuffer, (uint8_t)sizeof(OutBuffer)); 199 | // digitalWrite(GPIO_PIN_NSS, HIGH); 200 | } 201 | 202 | void SX1280Hal::ReadBuffer(uint8_t offset, volatile uint8_t *buffer, uint8_t size) 203 | { 204 | uint8_t OutBuffer[size + 3]; 205 | 206 | OutBuffer[0] = SX1280_RADIO_READ_BUFFER; 207 | OutBuffer[1] = offset; 208 | OutBuffer[2] = 0x00; 209 | 210 | memset(OutBuffer + 3, 0, size); // XXX is this needed? 211 | 212 | WaitOnBusy(); 213 | 214 | spi1_transferBytes(OutBuffer, uint8_t(sizeof(OutBuffer))); 215 | 216 | memcpy((void *)buffer, OutBuffer + 3, size); 217 | } 218 | 219 | /** Wait for the SX1280 busy flag to be low 220 | * Returns true if we reach the timeout before busy goes low 221 | * TODO pass in the timeout 222 | */ 223 | bool SX1280Hal::WaitOnBusy() 224 | { 225 | // printf("%s \r\n", "waitOnBusy..."); 226 | const uint MAX_WAIT = 1000; // in us 227 | const unsigned long t0 = micros(); 228 | while (gpio_input_bit_get(RADIO_BUSY_PORT, RADIO_BUSY_PIN) == SET) 229 | { 230 | if (micros() > (t0 + MAX_WAIT)) { 231 | printf("busy timeout \n"); 232 | return true; 233 | } 234 | } 235 | // printf("waitOnBusy done in %lu us\n", micros()-t0); 236 | return false; 237 | } 238 | 239 | // void SX1280Hal::dioISR() 240 | // { 241 | // if (instance->InterruptAssignment == SX1280_INTERRUPT_RX_DONE) 242 | // { 243 | // //Serial.println("HalRXdone"); 244 | // RXdoneCallback(); 245 | // } 246 | // else if (instance->InterruptAssignment == SX1280_INTERRUPT_TX_DONE) 247 | // { 248 | // //Serial.println("HalTXdone"); 249 | // TXdoneCallback(); 250 | // } 251 | // } 252 | 253 | void SX1280Hal::TXenable() 254 | { 255 | #if defined(RADIO_RXEN_PIN) 256 | gpio_bit_reset(RADIO_RXEN_PORT, RADIO_RXEN_PIN); 257 | #endif 258 | 259 | #if defined(RADIO_TXEN_PIN) 260 | gpio_bit_set(RADIO_TXEN_PORT, RADIO_TXEN_PIN); 261 | #endif 262 | } 263 | 264 | void SX1280Hal::RXenable() 265 | { 266 | #if defined(RADIO_RXEN_PIN) 267 | gpio_bit_set(RADIO_RXEN_PORT, RADIO_RXEN_PIN); 268 | #endif 269 | 270 | #if defined(RADIO_TXEN_PIN) 271 | gpio_bit_reset(RADIO_TXEN_PORT, RADIO_TXEN_PIN); 272 | #endif 273 | } 274 | 275 | void SX1280Hal::TXRXdisable() 276 | { 277 | #if defined(RADIO_RXEN_PIN) 278 | gpio_bit_reset(RADIO_RXEN_PORT, RADIO_RXEN_PIN); 279 | #endif 280 | 281 | #if defined(RADIO_TXEN_PIN) 282 | gpio_bit_reset(RADIO_TXEN_PORT, RADIO_TXEN_PIN); 283 | #endif 284 | } 285 | 286 | // void SX1280Hal::setIRQassignment(SX1280_InterruptAssignment_ newInterruptAssignment) 287 | // { 288 | 289 | // // if (InterruptAssignment == newInterruptAssignment) 290 | // // { 291 | // // return; 292 | // // } 293 | // // else 294 | // // { 295 | // if (newInterruptAssignment == SX1280_INTERRUPT_TX_DONE) 296 | // { 297 | // this->InterruptAssignment = SX1280_INTERRUPT_TX_DONE; 298 | // } 299 | // else if (newInterruptAssignment == SX1280_INTERRUPT_RX_DONE) 300 | // { 301 | // this->InterruptAssignment = SX1280_INTERRUPT_RX_DONE; 302 | // } 303 | // //} 304 | // } -------------------------------------------------------------------------------- /Firmware/lib/SX1280Driver/SX1280_hal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | ______ _ 5 | / _____) _ | | 6 | ( (____ _____ ____ _| |_ _____ ____| |__ 7 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 8 | _____) ) ____| | | || |_| ____( (___| | | | 9 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 10 | (C)2015 Semtech 11 | 12 | Description: Handling of the node configuration protocol 13 | 14 | License: Revised BSD License, see LICENSE.TXT file include in the project 15 | 16 | Maintainer: Miguel Luis and Gregory Cristian 17 | 18 | Heavily modified/simplified by Alessandro Carcione 2020 for ELRS project 19 | */ 20 | 21 | #define ICACHE_RAM_ATTR 22 | 23 | // E28-20 max is -2 24 | // #define MAX_PRE_PA_POWER -2 25 | 26 | // E28-27 max is 0 27 | // #define MAX_PRE_PA_POWER 0 28 | 29 | 30 | #include 31 | #include "SX1280_Regs.h" 32 | // #include "SX1280.h" 33 | // #include 34 | 35 | extern "C" { 36 | #include "gd32vf103.h" 37 | } 38 | 39 | // #define GPIO_PIN_BUSY foo 40 | // #define GPIO_PORT_BUSY bar 41 | 42 | enum SX1280_InterruptAssignment_ 43 | { 44 | SX1280_INTERRUPT_NONE, 45 | SX1280_INTERRUPT_RX_DONE, 46 | SX1280_INTERRUPT_TX_DONE 47 | }; 48 | 49 | enum SX1280_BusyState_ 50 | { 51 | SX1280_NOT_BUSY = true, 52 | SX1280_BUSY = false, 53 | }; 54 | 55 | class SX1280Hal 56 | { 57 | 58 | private: 59 | // volatile SX1280_InterruptAssignment_ InterruptAssignment = SX1280_INTERRUPT_NONE; 60 | //SX1280_BusyState_ BusyState = SX1280_NOT_BUSY; 61 | 62 | public: 63 | static SX1280Hal *instance; 64 | 65 | SX1280Hal(); 66 | 67 | void init(); 68 | void end(); 69 | // void SetSpiSpeed(uint32_t spiSpeed); 70 | void reset(); 71 | 72 | void ICACHE_RAM_ATTR WriteCommand(SX1280_RadioCommands_t opcode, uint8_t *buffer, uint8_t size); 73 | void ICACHE_RAM_ATTR fastWriteCommand(uint8_t *buffer, uint8_t size); 74 | 75 | void ICACHE_RAM_ATTR WriteCommand(SX1280_RadioCommands_t command, uint8_t val); 76 | void ICACHE_RAM_ATTR WriteRegister(uint16_t address, uint8_t *buffer, uint8_t size); 77 | void ICACHE_RAM_ATTR WriteRegister(uint16_t address, uint8_t value); 78 | 79 | void ICACHE_RAM_ATTR ReadCommand(SX1280_RadioCommands_t opcode, uint8_t *buffer, uint8_t size); 80 | void ICACHE_RAM_ATTR ReadRegister(uint16_t address, uint8_t *buffer, uint8_t size); 81 | uint8_t ICACHE_RAM_ATTR ReadRegister(uint16_t address); 82 | 83 | void ICACHE_RAM_ATTR WriteBuffer(uint8_t offset, volatile uint8_t *buffer, uint8_t size); // Writes and Reads to FIFO 84 | void ICACHE_RAM_ATTR ReadBuffer(uint8_t offset, volatile uint8_t *buffer, uint8_t size); 85 | 86 | // static void ICACHE_RAM_ATTR nullCallback(void); 87 | 88 | bool ICACHE_RAM_ATTR WaitOnBusy(); 89 | // static ICACHE_RAM_ATTR void dioISR(); 90 | 91 | void ICACHE_RAM_ATTR TXenable(); 92 | void ICACHE_RAM_ATTR RXenable(); 93 | void ICACHE_RAM_ATTR TXRXdisable(); 94 | 95 | // void ICACHE_RAM_ATTR setIRQassignment(SX1280_InterruptAssignment_ newInterruptAssignment); 96 | 97 | // static void (*TXdoneCallback)(); //function pointer for callback 98 | // static void (*RXdoneCallback)(); //function pointer for callback 99 | }; 100 | -------------------------------------------------------------------------------- /Firmware/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:sipeed-longan-nano] 12 | platform = gd32v 13 | ; platform = https://github.com/sipeed/platform-gd32v.git 14 | board = sipeed-longan-nano 15 | framework = gd32vf103-sdk 16 | build_unflags = -Os 17 | build_flags = -O2 18 | -D MY_UID=0x17,0x13,0x31,0x19,0x23,0xEB 19 | ; -D USE_FLRC 20 | 21 | ;upload_protocol = dfu 22 | 23 | upload_protocol = serial 24 | ; upload_port = /dev/ttyUSB1 25 | upload_speed = 460800 26 | 27 | debug_tool = sipeed-rv-debugger 28 | ; upload_protocol = sipeed-rv-debugger 29 | 30 | monitor_speed = 460800 31 | ; monitor_port = /dev/ttyUSB1 -------------------------------------------------------------------------------- /Firmware/src/FHSS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FHSS.h" 3 | #include "Serial.h" 4 | 5 | uint8_t volatile FHSSptr = 0; // XXX must be 8 bit unsigned to wrap when incremented, 6 | #if (NR_SEQUENCE_ENTRIES != 256) 7 | #error "hop sequence table must be 256 entries" 8 | #endif 9 | uint8_t FHSSsequence[NR_SEQUENCE_ENTRIES] = {0}; 10 | 11 | //uint8_t NumOfFHSSfrequencies = 20; 12 | int32_t FreqCorrection = 0; 13 | 14 | void ICACHE_RAM_ATTR FHSSsetCurrIndex(uint8_t value) 15 | { // set the current index of the FHSS pointer 16 | FHSSptr = value; 17 | } 18 | 19 | uint8_t ICACHE_RAM_ATTR FHSSgetCurrIndex() 20 | { // get the current index of the FHSS pointer 21 | return FHSSptr; 22 | } 23 | 24 | uint32_t ICACHE_RAM_ATTR GetInitialFreq() 25 | { 26 | return FHSSfreqs[0] - FreqCorrection; 27 | } 28 | 29 | uint32_t ICACHE_RAM_ATTR FHSSgetCurrFreq() 30 | { 31 | return FHSSfreqs[FHSSsequence[FHSSptr]] - FreqCorrection; 32 | } 33 | 34 | uint32_t ICACHE_RAM_ATTR FHSSgetNextFreq() 35 | { 36 | FHSSptr++; // as long as FHSSptr is uint8 it will wrap without extra code 37 | return FHSSgetCurrFreq(); 38 | } 39 | 40 | // Set all of the flags in the array to true, except for the first one 41 | // which corresponds to the sync channel and is never available for normal 42 | // allocation. 43 | void ICACHE_RAM_ATTR resetIsAvailable(uint8_t *array) 44 | { 45 | // channel 0 is the sync channel and is never considered available 46 | array[0] = 0; 47 | 48 | // all other entires to 1 49 | for (unsigned int i = 1; i < NR_FHSS_ENTRIES; i++) 50 | array[i] = 1; 51 | } 52 | 53 | /** 54 | Requirements: 55 | 1. 0 every n hops 56 | 2. No two repeated channels 57 | 3. Equal occurance of each (or as even as possible) of each channel 58 | 4. Pesudorandom 59 | 60 | Approach: 61 | Initialise an array of flags indicating which channels have not yet been assigned and a counter of how many channels are available 62 | Iterate over the FHSSsequence array using index 63 | if index is a multiple of SYNC_INTERVAL assign the sync channel index (0) 64 | otherwise, generate a random number between 0 and the number of channels left to be assigned 65 | find the index of the nth remaining channel 66 | if the index is a repeat, generate a new random number 67 | if the index is not a repeat, assing it to the FHSSsequence array, clear the availability flag and decrement the available count 68 | if there are no available channels left, reset the flags array and the count 69 | */ 70 | void ICACHE_RAM_ATTR FHSSrandomiseFHSSsequence() 71 | { 72 | 73 | #ifdef Regulatory_Domain_AU_915 74 | Serial.println("Setting 915MHz Mode"); 75 | #elif defined Regulatory_Domain_FCC_915 76 | Serial.println("Setting 915MHz Mode"); 77 | #elif defined Regulatory_Domain_EU_868 78 | Serial.println("Setting 868MHz Mode"); 79 | #elif defined Regulatory_Domain_AU_433 || defined Regulatory_Domain_EU_433 80 | Serial.println("Setting 433MHz Mode"); 81 | #elif defined Regulatory_Domain_ISM_2400 || defined Regulatory_Domain_ISM_2400_NA 82 | Serial.println("Setting 2400MHz Mode"); 83 | #else 84 | #error No regulatory domain defined, please define one in common.h 85 | #endif 86 | 87 | Serial.print("Number of FHSS frequencies ="); 88 | Serial.println(NR_FHSS_ENTRIES); 89 | 90 | long macSeed = ((long)UID[2] << 24) + ((long)UID[3] << 16) + ((long)UID[4] << 8) + UID[5]; 91 | rngSeed(macSeed); 92 | // srandom(macSeed); 93 | 94 | uint8_t isAvailable[NR_FHSS_ENTRIES]; 95 | 96 | resetIsAvailable(isAvailable); 97 | 98 | // Fill the FHSSsequence with channel indices 99 | // The 0 index is special - the 'sync' channel. The sync channel appears every 100 | // syncInterval hops. The other channels are randomly distributed between the 101 | // sync channels 102 | const int SYNC_INTERVAL = NR_FHSS_ENTRIES -1; 103 | 104 | int nLeft = NR_FHSS_ENTRIES - 1; // how many channels are left to be allocated. Does not include the sync channel 105 | unsigned int prev = 0; // needed to prevent repeats of the same index 106 | 107 | // for each slot in the sequence table 108 | for (int i = 0; i < NR_SEQUENCE_ENTRIES; i++) 109 | { 110 | if (i % SYNC_INTERVAL == 0) 111 | { 112 | // assign sync channel 0 113 | FHSSsequence[i] = 0; 114 | prev = 0; 115 | } 116 | else 117 | { 118 | // pick one of the available channels. May need to loop to avoid repeats 119 | unsigned int index; 120 | do 121 | { 122 | int c = rngN(nLeft); // returnc 0 4 | #include "config.h" 5 | 6 | #if defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_FCC_915) || defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433) 7 | #include "SX127xDriver.h" 8 | #elif defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 9 | #include "SX1280Driver.h" 10 | #else 11 | #error "Need to set Reg domain" 12 | #endif 13 | 14 | #include "utils.h" 15 | #include "common.h" 16 | 17 | extern volatile uint8_t FHSSptr; 18 | 19 | extern uint8_t NumOfFHSSfrequencies; 20 | 21 | extern int32_t FreqCorrection; 22 | #define FreqCorrectionMax 200000 23 | #define FreqCorrectionMin -200000 24 | 25 | void ICACHE_RAM_ATTR FHSSsetCurrIndex(uint8_t value); 26 | 27 | uint8_t ICACHE_RAM_ATTR FHSSgetCurrIndex(); 28 | 29 | // Our table of FHSS frequencies. Define a regulatory domain to select the correct set for your location and radio 30 | #ifdef Regulatory_Domain_AU_433 31 | const uint32_t FHSSfreqs[] = { 32 | 433420000, 33 | 433920000, 34 | 434420000}; 35 | #elif defined Regulatory_Domain_AU_915 36 | const uint32_t FHSSfreqs[] = { 37 | 915500000, 38 | 916100000, 39 | 916700000, 40 | 917300000, 41 | 42 | 917900000, 43 | 918500000, 44 | 919100000, 45 | 919700000, 46 | 47 | 920300000, 48 | 920900000, 49 | 921500000, 50 | 922100000, 51 | 52 | 922700000, 53 | 923300000, 54 | 923900000, 55 | 924500000, 56 | 57 | 925100000, 58 | 925700000, 59 | 926300000, 60 | 926900000}; 61 | #elif defined Regulatory_Domain_EU_868 62 | /* Frequency bands taken from https://wetten.overheid.nl/BWBR0036378/2016-12-28#Bijlagen 63 | * Note: these frequencies fall in the license free H-band, but in combination with 500kHz 64 | * LoRa modem bandwidth used by ExpressLRS (EU allows up to 125kHz modulation BW only) they 65 | * will never pass RED certification and they are ILLEGAL to use. 66 | * 67 | * Therefore we simply maximize the usage of available spectrum so laboratory testing of the software won't disturb existing 68 | * 868MHz ISM band traffic too much. 69 | */ 70 | const uint32_t FHSSfreqs[] = { 71 | 863275000, // band H1, 863 - 865MHz, 0.1% duty cycle or CSMA techniques, 25mW EIRP 72 | 863800000, 73 | 864325000, 74 | 864850000, 75 | 865375000, // Band H2, 865 - 868.6MHz, 1.0% dutycycle or CSMA, 25mW EIRP 76 | 865900000, 77 | 866425000, 78 | 866950000, 79 | 867475000, 80 | 868000000, 81 | 868525000, // Band H3, 868.7-869.2MHz, 0.1% dutycycle or CSMA, 25mW EIRP 82 | 869050000, 83 | 869575000}; 84 | #elif defined Regulatory_Domain_EU_433 85 | /* Frequency band G, taken from https://wetten.overheid.nl/BWBR0036378/2016-12-28#Bijlagen 86 | * Note: As is the case with the 868Mhz band, these frequencies only comply to the license free portion 87 | * of the spectrum, nothing else. As such, these are likely illegal to use. 88 | */ 89 | const uint32_t FHSSfreqs[] = { 90 | 433100000, 91 | 433925000, 92 | 434450000}; 93 | #elif defined Regulatory_Domain_FCC_915 94 | /* Very definitely not fully checked. An initial pass at increasing the hops 95 | */ 96 | const uint32_t FHSSfreqs[] = { 97 | 903500000, 98 | 904100000, 99 | 904700000, 100 | 905300000, 101 | 102 | 905900000, 103 | 906500000, 104 | 907100000, 105 | 907700000, 106 | 107 | 908300000, 108 | 908900000, 109 | 909500000, 110 | 910100000, 111 | 112 | 910700000, 113 | 911300000, 114 | 911900000, 115 | 912500000, 116 | 117 | 913100000, 118 | 913700000, 119 | 914300000, 120 | 914900000, 121 | 122 | 915500000, // as per AU.. 123 | 916100000, 124 | 916700000, 125 | 917300000, 126 | 127 | 917900000, 128 | 918500000, 129 | 919100000, 130 | 919700000, 131 | 132 | 920300000, 133 | 920900000, 134 | 921500000, 135 | 922100000, 136 | 137 | 922700000, 138 | 923300000, 139 | 923900000, 140 | 924500000, 141 | 142 | 925100000, 143 | 925700000, 144 | 926300000, 145 | 926900000}; 146 | #elif defined(Regulatory_Domain_ISM_2400_NA) 147 | // a more conservative frequency table with wider spacing. 148 | const uint32_t FHSSfreqs[] = { 149 | 2425000000, 150 | 2402000000, 151 | 2403000000, 152 | 2404000000, 153 | 2405000000, 154 | 2406000000, 155 | 2407000000, 156 | 2408000000, 157 | 2409000000, 158 | 2410000000, 159 | 2411000000, 160 | 2412000000, 161 | 2413000000, 162 | 2414000000, 163 | 2415000000, 164 | 2416000000, 165 | 2417000000, 166 | 2418000000, 167 | 2419000000, 168 | 2420000000, 169 | 2421000000, 170 | 2422000000, 171 | 2423000000, 172 | 2424000000, 173 | 2426000000, 174 | 2427000000, 175 | 2428000000, 176 | 2429000000, 177 | 2430000000, 178 | 2431000000, 179 | 2432000000, 180 | 2433000000, 181 | 2434000000, 182 | 2435000000, 183 | 2436000000, 184 | 2437000000, 185 | 2438000000, 186 | 2439000000, 187 | 2440000000, 188 | 2441000000, 189 | 2442000000, 190 | 2443000000, 191 | 2444000000, 192 | 2445000000, 193 | 2446000000, 194 | 2447000000, 195 | 2448000000, 196 | }; 197 | #elif defined(Regulatory_Domain_ISM_2400) 198 | const uint32_t FHSSfreqs[] = { 199 | 2400400000, 200 | 2401400000, 201 | 2402400000, 202 | 2403400000, 203 | 2404400000, 204 | 205 | 2405400000, 206 | 2406400000, 207 | 2407400000, 208 | 2408400000, 209 | 2409400000, 210 | 211 | 2410400000, 212 | 2411400000, 213 | 2412400000, 214 | 2413400000, 215 | 2414400000, 216 | 217 | 2415400000, 218 | 2416400000, 219 | 2417400000, 220 | 2418400000, 221 | 2419400000, 222 | 223 | 2420400000, 224 | 2421400000, 225 | 2422400000, 226 | 2423400000, 227 | 2424400000, 228 | 229 | 2425400000, 230 | 2426400000, 231 | 2427400000, 232 | 2428400000, 233 | 2429400000, 234 | 235 | 2430400000, 236 | 2431400000, 237 | 2432400000, 238 | 2433400000, 239 | 2434400000, 240 | 241 | 2435400000, 242 | 2436400000, 243 | 2437400000, 244 | 2438400000, 245 | 2439400000, 246 | 247 | 2440400000, 248 | 2441400000, 249 | 2442400000, 250 | 2443400000, 251 | 2444400000, 252 | 253 | 2445400000, 254 | 2446400000, 255 | 2447400000, 256 | 2448400000, 257 | 2449400000, 258 | 259 | 2450400000, 260 | 2451400000, 261 | 2452400000, 262 | 2453400000, 263 | 2454400000, 264 | 265 | 2455400000, 266 | 2456400000, 267 | 2457400000, 268 | 2458400000, 269 | 2459400000, 270 | 271 | 2460400000, 272 | 2461400000, 273 | 2462400000, 274 | 2463400000, 275 | 2464400000, 276 | 277 | 2465400000, 278 | 2466400000, 279 | 2467400000, 280 | 2468400000, 281 | 2469400000, 282 | 283 | 2470400000, 284 | 2471400000, 285 | 2472400000, 286 | 2473400000, 287 | 2474400000, 288 | 289 | 2475400000, 290 | 2476400000, 291 | 2477400000, 292 | 2478400000, 293 | 2479400000}; 294 | #else 295 | #error No regulatory domain defined, please define one in user_defines.txt 296 | #endif 297 | 298 | // The number of FHSS frequencies in the table 299 | #define NR_FHSS_ENTRIES (sizeof(FHSSfreqs) / sizeof(uint32_t)) 300 | 301 | // XXX We rely on this being 256 to match the wrap on an 8 bit index 302 | #define NR_SEQUENCE_ENTRIES 256 303 | extern uint8_t FHSSsequence[NR_SEQUENCE_ENTRIES]; 304 | 305 | uint32_t ICACHE_RAM_ATTR GetInitialFreq(); 306 | uint32_t ICACHE_RAM_ATTR FHSSgetCurrFreq(); 307 | uint32_t ICACHE_RAM_ATTR FHSSgetNextFreq(); 308 | void ICACHE_RAM_ATTR FHSSrandomiseFHSSsequence(); -------------------------------------------------------------------------------- /Firmware/src/Serial.cpp: -------------------------------------------------------------------------------- 1 | 2 | //#include "Serial.h" 3 | 4 | // code moved into header for inlining -------------------------------------------------------------------------------- /Firmware/src/Serial.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | 6 | class SerialWrapper 7 | { 8 | public: 9 | void print(const char * str); 10 | void println(const char * str); 11 | void print(unsigned int x); 12 | void println(unsigned int x); 13 | void println(void); 14 | 15 | }; 16 | 17 | // ============================================================= 18 | // impl for inlining 19 | 20 | void SerialWrapper::print(const char * str) 21 | { 22 | printf("%s", str); 23 | } 24 | 25 | void SerialWrapper::println(const char * str) 26 | { 27 | printf("%s\n\r", str); 28 | } 29 | 30 | void SerialWrapper::print(const unsigned int x) 31 | { 32 | printf("%u", x); 33 | } 34 | 35 | void SerialWrapper::println(const unsigned int x) 36 | { 37 | printf("%u\n\r", x); 38 | } 39 | 40 | void SerialWrapper::println(void) 41 | { 42 | printf("\n\r"); 43 | } 44 | 45 | 46 | 47 | 48 | // The usage we're trying to mimick had a global instance called 'Serial' 49 | SerialWrapper Serial; 50 | 51 | -------------------------------------------------------------------------------- /Firmware/src/SimpleStore.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "SimpleStore.h" 8 | 9 | extern "C" { 10 | #include "gd32vf103.h" 11 | #include "systick.h" 12 | } 13 | 14 | 15 | // internal version of write that doesn't handle updates 16 | // returns SS_NO_SPACE if there isn't room for the data 17 | // returns SS_OK on success 18 | // returns any error codes returned by addressOfFreespace() 19 | uint8_t SimpleStore::_write(const uint16_t id, const uint16_t length, void *data) 20 | { 21 | // check that there is enough space for the data (including any extra byte so that we can write half words) 22 | uint32_t spaceNeeded; 23 | // space needed for data may need rounding up if not a multiple of 2 24 | // include 4 bytes for id and length fields 25 | if (length % 2 != 0) { 26 | spaceNeeded = length + 5; // round up so we have a spare byte for writing the last value in a half word 27 | } else { 28 | spaceNeeded = length + 4; 29 | } 30 | 31 | if (spaceNeeded > freespaceRemaining()) { 32 | // not going to fit, return error code 33 | return SS_NO_SPACE; 34 | } 35 | 36 | // find the address to write the next object to 37 | uint32_t objAddr = addressOfFreespace(); 38 | // we've already checked that there is enough space for the data, but might 39 | // return SS_MISSING_EYECATCHER 40 | if ((uint32_t)objAddr < 256) { // error codes are small values, valid addresses are large 41 | return (uint8_t) objAddr; 42 | } 43 | 44 | // printf("_write, writing obj at %lx\n\r", objAddr); 45 | 46 | // have to unlock to be able to write 47 | fmc_unlock(); 48 | 49 | // write the id 50 | fmc_halfword_program(objAddr, id); 51 | 52 | // write the length 53 | fmc_halfword_program(objAddr+2, length); 54 | 55 | objAddr += 4; // objAddr is an int not a pointer, so we can move it in 1 byte units 56 | 57 | // from the examples. Presumably we should actually be checking for errors? 58 | fmc_flag_clear(FMC_FLAG_END); 59 | fmc_flag_clear(FMC_FLAG_WPERR); 60 | fmc_flag_clear(FMC_FLAG_PGERR); 61 | 62 | // write the data. We can only write words and halfwords. Fortunately we only write to the end of the storage 63 | // space, so we don't have to worry about overwriting the next entry. 64 | // For now, just write halfwords, we can worry about optimising for full words later. 65 | uint32_t halfWords = (length + 1) / 2; // +1 to round up 66 | 67 | uint16_t * ptr = (uint16_t *) data; 68 | for(uint32_t i=0; i= (SS_N_PAGES * FLASH_PAGE_SIZE - 4)) { // -4 to allow for the eyecatcher 106 | // gc isn't going to help so we might as well skip the wear and tear on the flash 107 | return SS_NO_SPACE; 108 | } 109 | 110 | uint8_t * copyBuffer = (uint8_t *)malloc(liveSize); 111 | 112 | if (copyBuffer == NULL) { 113 | printf("gc: failed to malloc buffer for %lu\n\r", liveSize); 114 | return SS_NO_SPACE; 115 | } 116 | 117 | // copy all the live records into the buffer 118 | uint8_t * copyPtr = copyBuffer; 119 | 120 | while(p < (uint8_t*)((SS_START_ADDRESS + (SS_N_PAGES * FLASH_PAGE_SIZE)) - 1)) 121 | { 122 | // Is the current object in the freespace? All real objects have a uid != 0xFFFF 123 | if (*((uint16_t *)p) == 0xFFFF) { 124 | break; // leave the loop 125 | } 126 | 127 | // read the length so we can adjust the pointer to the next object 128 | uint16_t objLength = *((uint16_t *)(p+2)); 129 | 130 | // records are aligned to even addresses, so round up if necessary 131 | if (objLength % 2 != 0) objLength++; 132 | 133 | objLength += 4; // +4 to skip the id and length of this object 134 | 135 | // is the current object live? 136 | if (*((uint16_t *)p) != 0) { 137 | // copy it to the buffer 138 | memcpy(copyPtr, p, objLength); 139 | copyPtr += objLength; 140 | } 141 | 142 | p += objLength; 143 | } 144 | 145 | // printf("gc collected %lu bytes of live records\n\r", ((uint32_t)copyPtr - (uint32_t)copyBuffer)); 146 | 147 | fmc_unlock(); 148 | 149 | // erase the page 150 | fmc_page_erase(SS_START_ADDRESS); 151 | 152 | // write the eyecatcher 153 | fmc_word_program(SS_START_ADDRESS, SS_EYECATCHER); 154 | 155 | // write all the records back to storage 156 | uint32_t nHalfwords = ((copyPtr - copyBuffer) + 1)/2; 157 | uint32_t destAddr = SS_START_ADDRESS + 4; 158 | copyPtr = copyBuffer; 159 | for (uint32_t i=0; i < nHalfwords; i++) 160 | { 161 | fmc_halfword_program(destAddr, *((uint16_t*)copyPtr)); 162 | destAddr += 2; 163 | copyPtr += 2; 164 | } 165 | 166 | fmc_lock(); 167 | 168 | // free the copy buffer 169 | free(copyBuffer); 170 | 171 | return SS_OK; 172 | } 173 | 174 | // read the specified object from the storage area 175 | uint8_t SimpleStore::read(const uint16_t id, uint16_t expectedLength, void *data) 176 | { 177 | uint32_t objAddr = addressOf(id); 178 | 179 | // valid addresses are large, error codes are small 180 | if (objAddr < 256) { 181 | return (uint8_t)objAddr; // EARLY RETURN 182 | } 183 | 184 | // printf("read: reading object at %lx\n\r", objAddr); 185 | 186 | // validate the length matches the space we've been provided to return the data in 187 | uint16_t storedLen = *((uint16_t*)(objAddr+2)); 188 | if (storedLen != expectedLength) { 189 | return SS_LENGTH_MISMATCH; 190 | } 191 | 192 | // copy the data into the provided buffer 193 | memcpy(data, (void *)(objAddr + 4), storedLen); 194 | 195 | return SS_OK; 196 | } 197 | 198 | /** Create a new record with the given id 199 | * Will remove any existing record for that id, so can be used for updates 200 | * Will initialise the storage area on first use, provided that the memory is previously untouched 201 | */ 202 | uint8_t SimpleStore::write(const uint16_t id, const uint16_t length, void *data) 203 | { 204 | uint8_t result = remove(id); 205 | 206 | if (result == SS_MISSING_EYECATCHER) 207 | { 208 | // check if the storage area is untouched 209 | if (storageIsClean()) { 210 | // write the eyecatcher to the first word 211 | fmc_unlock(); 212 | fmc_word_program(SS_START_ADDRESS, SS_EYECATCHER); 213 | fmc_flag_clear(FMC_FLAG_END); 214 | fmc_flag_clear(FMC_FLAG_WPERR); 215 | fmc_flag_clear(FMC_FLAG_PGERR); 216 | fmc_lock(); 217 | } 218 | } // missing eyecatcher - storage wasn't initialised 219 | 220 | return _write(id, length, data); 221 | } 222 | 223 | /** Remove the specified record 224 | * @return SS_OK on success, SS_NOT_FOUND if no record for that id present in the store, 225 | * SS_MISSING_EYECATCHER if the store has not been initialised 226 | */ 227 | uint8_t SimpleStore::remove(const uint16_t id) 228 | { 229 | // get the address of the object, if it exists 230 | uint32_t objAddr = addressOf(id); 231 | 232 | if (objAddr < 256) { // return any error conditions to the caller 233 | return (uint8_t) objAddr; 234 | } 235 | 236 | // printf("removing obj at %lx\n\r", objAddr); 237 | 238 | // overwrite the id with reserved value 0x00 239 | 240 | // have to unlock to be able to write 241 | fmc_unlock(); 242 | 243 | // write the id and length as a halfword 244 | fmc_state_enum res = fmc_halfword_program(objAddr, 0); 245 | 246 | if (res) {} // just to hush the compiler warning when the debug is commented out 247 | 248 | // printf("res %d\n\r", res); 249 | 250 | // from the examples. Presumably we should actually be checking for errors? 251 | fmc_flag_clear(FMC_FLAG_END); 252 | fmc_flag_clear(FMC_FLAG_WPERR); 253 | fmc_flag_clear(FMC_FLAG_PGERR); 254 | 255 | fmc_lock(); 256 | 257 | // delay(10); 258 | 259 | // uint32_t firstWord = *((uint32_t*)objAddr); 260 | // printf("firstword after program 0x%lx\n\r", firstWord); // the id is in the lowest bytes 261 | 262 | return SS_OK; 263 | } 264 | 265 | /** return the address of the specified object 266 | * @return address if the object is in the store, SS_NOT_FOUND or SS_MISSING_EYECATCHER 267 | */ 268 | uint32_t SimpleStore::addressOf(const uint16_t id) 269 | { 270 | uint8_t *p = (uint8_t*)SS_START_ADDRESS; 271 | 272 | if (*(uint32_t*)p != SS_EYECATCHER) return SS_MISSING_EYECATCHER; 273 | 274 | p += 4; 275 | 276 | while(p < (uint8_t*)((SS_START_ADDRESS + (SS_N_PAGES * FLASH_PAGE_SIZE)) - 1)) 277 | { 278 | // printf("%p id %u\n\r", p, *((uint16_t *)p)); 279 | 280 | // Does the ID of the current object match the specified value? 281 | if ( *((uint16_t *)p) == id) { 282 | return (uint32_t)p; // EARLY RETURN 283 | } 284 | 285 | // have we reached the freespace? An unassigned id will contain 0xFF (the flash erase value) 286 | if ( *((uint16_t *)p) == 0xFF) { 287 | return SS_NOT_FOUND; // EARLY RETURN 288 | } 289 | 290 | // read the length so we can adjust the pointer to the next object 291 | uint16_t objlength = *((uint16_t *)(p+2)); 292 | // printf("len %u\n\r", objlength); 293 | 294 | p += objlength + 4; // +4 to skip the id and length of this object 295 | 296 | // records are aligned to even addresses, so adjust p if necessary 297 | if ((uint32_t)p % 2 != 0) p++; 298 | } 299 | 300 | return SS_NOT_FOUND; 301 | } 302 | 303 | /** Find the start of unused space in the storage area 304 | * 305 | * Walk the set of objects until we find one with the reserved ID 0xFF 306 | * The returned address will be aligned to an even (half-word) boundary. 307 | * 308 | * @return the first free (even aligned) address in the storage area or 309 | * SS_NO_SPACE if no freespace available 310 | * SS_MISSING_EYECATCHER if the eyecatcher wasn't found 311 | * 312 | */ 313 | uint32_t SimpleStore::addressOfFreespace() 314 | { 315 | uint8_t *p = (uint8_t*)SS_START_ADDRESS; 316 | 317 | if (*(uint32_t*)p != SS_EYECATCHER) return SS_MISSING_EYECATCHER; 318 | 319 | p += 4; 320 | 321 | while(p < (uint8_t*)((SS_START_ADDRESS + (SS_N_PAGES * FLASH_PAGE_SIZE)) - 1)) 322 | { 323 | // Is the current object in the freespace? All real objects have a uid != 0xFF 324 | if (*((uint16_t *)p) == 0xFFFF) { 325 | return (uint32_t)p; // EARLY RETURN 326 | } 327 | 328 | // read the length so we can adjust the pointer to the next object 329 | uint16_t objlength = *((uint16_t *)(p+2)); 330 | p += objlength + 4; // +4 to skip the id and length of this object 331 | 332 | // records are aligned to even addresses, so adjust p if necessary 333 | if ((uint32_t)p % 2 != 0) p++; 334 | } 335 | 336 | return SS_NO_SPACE; 337 | } 338 | 339 | // Return the number of bytes available for new entries in the store 340 | uint32_t SimpleStore::freespaceRemaining() 341 | { 342 | uint32_t freeStart = addressOfFreespace(); 343 | uint32_t storeEnd = FLASH_PAGE_SIZE * SS_N_PAGES + SS_START_ADDRESS; 344 | uint32_t space = storeEnd - freeStart; 345 | return space; 346 | } 347 | 348 | /** total storage used by live objects 349 | * Returns the number of bytes needed to hold the live objects, including the headers. 350 | * Doesn't check for a valid eyecatcher at the start of the store. 351 | * 352 | * There's a problem with returning error codes as the minimum valid result is 4 (or 5 if we 353 | * forbid 0 length objects), and this could easily overlap with the small unsigned ints used 354 | * everywhere else for return codes. 355 | */ 356 | uint32_t SimpleStore::sizeofLiveObjects() 357 | { 358 | uint8_t *p = (uint8_t*)(SS_START_ADDRESS + 4); // +4 to skip the eyecatcher, which we're going to assume is ok 359 | 360 | uint32_t totalSize = 0; 361 | 362 | while(p < (uint8_t*)((SS_START_ADDRESS + (SS_N_PAGES * FLASH_PAGE_SIZE)) - 1)) 363 | { 364 | // Is the current object in the freespace? All real objects have a uid != 0xFFFF 365 | if (*((uint16_t *)p) == 0xFFFF) { 366 | break; // leave the loop 367 | } 368 | 369 | // read the length so we can adjust the pointer to the next object 370 | uint16_t objLength = *((uint16_t *)(p+2)); 371 | 372 | // records are aligned to even addresses, so round up if necessary 373 | if (objLength % 2 != 0) objLength++; 374 | 375 | objLength += 4; // +4 to skip the id and length of this object 376 | 377 | // is the current object live? 378 | if (*((uint16_t *)p) != 0) { 379 | totalSize += objLength; 380 | } 381 | 382 | p += objLength; 383 | } 384 | 385 | // might get here if the store was completely full 386 | return totalSize; 387 | } 388 | 389 | bool SimpleStore::storageIsClean() 390 | { 391 | uint32_t * p = (uint32_t *) SS_START_ADDRESS; 392 | uint32_t nWords = SS_N_PAGES * FLASH_PAGE_SIZE / 4; 393 | 394 | while(nWords > 0) { 395 | if (*p != 0xFFFFFFFF) return false; 396 | p++; // nasty pointer arithmetic will increase the address by the size of the type in the pointer declaration 397 | nWords--; 398 | } 399 | 400 | // if we made it to here then everything must be 0xFF, which means untouched flash 401 | return true; 402 | } 403 | 404 | //=============================== 405 | 406 | void SimpleStore::test() 407 | { 408 | printf("flash read, checking page is clean...\n\r"); 409 | 410 | // read words from start address and check they're all 0xFFFFFFFF 411 | uint32_t *p = (uint32_t *) SS_START_ADDRESS; 412 | uint32_t badwords = 0; 413 | for(int i=0; i<(SS_N_PAGES*FLASH_PAGE_SIZE/4); i++) 414 | { 415 | 416 | if (*p != 0xFFFFFFFF) { 417 | badwords++; 418 | } 419 | p++; 420 | } 421 | 422 | printf("flash checked, non-FF words=%lu\n\r", badwords); 423 | 424 | // write the eyecatcher to the first word of storage (unless it's already there) 425 | 426 | printf("flash write test\n\r"); 427 | 428 | if (SS_EYECATCHER != *((uint32_t*)SS_START_ADDRESS)) { 429 | // write a value to the first word of the storage area 430 | fmc_unlock(); 431 | fmc_word_program(SS_START_ADDRESS, SS_EYECATCHER); 432 | 433 | // from the examples. Presumably we should actually be checking for errors? 434 | fmc_flag_clear(FMC_FLAG_END); 435 | fmc_flag_clear(FMC_FLAG_WPERR); 436 | fmc_flag_clear(FMC_FLAG_PGERR); 437 | 438 | fmc_lock(); 439 | 440 | delay(1); // needed? 441 | 442 | // read back and see what we got 443 | uint32_t *p = (uint32_t *) SS_START_ADDRESS; 444 | 445 | uint32_t x = *p; 446 | 447 | if (x == SS_EYECATCHER) { 448 | printf("success!\n\r"); 449 | } else { 450 | printf("read unexpected value %lu\n\r", x); 451 | } 452 | 453 | } else { 454 | printf("eyecatcher already in place\n\r"); 455 | } 456 | 457 | uint32_t freeAddr = addressOfFreespace(); 458 | printf("freeAddr %p\n\r", (void*)freeAddr); 459 | 460 | printf("storageIsClean returns %u\n\r", storageIsClean()); 461 | 462 | printf("space remaining %lu\n\r", freespaceRemaining()); 463 | 464 | printf("used %lu\n\r", sizeofLiveObjects()); 465 | 466 | uint32_t testCounter; 467 | 468 | #define TEST_ID 1 469 | uint8_t result; 470 | 471 | // try and remove any existing entries for the test object 472 | printf("removing any existing copies of the test object\n\r"); 473 | result = SS_OK; 474 | // since remove isn't working properly, don't make it loop yet 475 | // while(result == SS_OK) 476 | { 477 | result = remove(TEST_ID); 478 | delay(1); 479 | switch (result) { 480 | case SS_OK: 481 | printf("removed an instance of test object\n\r"); 482 | break; 483 | case SS_NOT_FOUND: 484 | printf("no instances found to remove\n\r"); 485 | break; 486 | default: 487 | printf("remove returned %u\n\r", result); 488 | } 489 | } 490 | 491 | // try and read a stored object - shouldn't be there if the remove worked 492 | printf("trying to read object, expect not found\n\r"); 493 | result = read(TEST_ID, 4, &testCounter); 494 | if (result == SS_OK) { 495 | printf("test object was found! value was %lx\n\r", testCounter); 496 | } else if (result == SS_NOT_FOUND) { 497 | printf("test object was not found\n\r"); 498 | } else { 499 | printf("read returned %u\n\r", result); 500 | } 501 | 502 | // write an object 503 | printf("writing object, expect ok\n\r"); 504 | testCounter = 0x12345678; 505 | result = write(TEST_ID, 4, &testCounter); 506 | switch (result) { 507 | case SS_OK: 508 | printf("write ok\n\r"); 509 | break; 510 | default: 511 | printf("write returned %u\n\r", result); 512 | } 513 | 514 | delay(1); 515 | 516 | // try and read the object 517 | printf("reading object back, expect ok with value 0x12345678\n\r"); 518 | result = read(TEST_ID, 4, &testCounter); 519 | if (result == SS_OK) { 520 | printf("test object was found! value was %lx\n\r", testCounter); 521 | } else if (result == SS_NOT_FOUND) { 522 | printf("test object was not found\n\r"); 523 | } else { 524 | printf("read returned %u\n\r", result); 525 | } 526 | 527 | printf("used %lu\n\r", sizeofLiveObjects()); 528 | 529 | 530 | // write the object with a new value 531 | printf("updating object with new value, expect ok\n\r"); 532 | testCounter = 0x87654321; 533 | result = write(TEST_ID, 4, &testCounter); 534 | switch (result) { 535 | case SS_OK: 536 | printf("write ok\n\r"); 537 | break; 538 | default: 539 | printf("write returned %u\n\r", result); 540 | } 541 | 542 | delay(1); 543 | 544 | // try and read the object 545 | printf("reading object back, expect ok with value 0x87654321\n\r"); 546 | result = read(TEST_ID, 4, &testCounter); 547 | if (result == SS_OK) { 548 | printf("test object was found! value was %lx\n\r", testCounter); 549 | } else if (result == SS_NOT_FOUND) { 550 | printf("test object was not found\n\r"); 551 | } else { 552 | printf("read returned %u\n\r", result); 553 | } 554 | 555 | printf("used %lu\n\r", sizeofLiveObjects()); 556 | 557 | 558 | // remove the object 559 | printf("removing object, expect ok\n\r"); 560 | result = remove(TEST_ID); 561 | switch (result) { 562 | case SS_OK: 563 | printf("remove ok\n\r"); 564 | break; 565 | case SS_NOT_FOUND: 566 | printf("remove couldn't find the object\n\r"); 567 | break; 568 | default: 569 | printf("remove returned %u\n\r", result); 570 | } 571 | 572 | delay(1); 573 | 574 | // try and read the object 575 | printf("reading object, expect NOT_FOUND\n\r"); 576 | result = read(TEST_ID, 4, &testCounter); 577 | if (result == SS_OK) { 578 | printf("test object was found! value was %lx\n\r", testCounter); 579 | } else if (result == SS_NOT_FOUND) { 580 | printf("test object has gone - congrats :)\n\r"); 581 | } else { 582 | printf("read returned %u\n\r", result); 583 | } 584 | 585 | printf("used %lu\n\r", sizeofLiveObjects()); 586 | printf("space remaining %lu\n\r", freespaceRemaining()); 587 | 588 | // write a couple of new records 589 | printf("writing 2 records\n\r"); 590 | 591 | testCounter = 0x11111111; 592 | write(2, 4, &testCounter); 593 | 594 | testCounter = 0x22222222; 595 | write(TEST_ID, 4, &testCounter); 596 | 597 | printf("used %lu\n\r", sizeofLiveObjects()); 598 | printf("space remaining %lu\n\r", freespaceRemaining()); 599 | 600 | printf("calling gc\n\r"); 601 | gc(); 602 | 603 | printf("used %lu\n\r", sizeofLiveObjects()); 604 | printf("space remaining %lu\n\r", freespaceRemaining()); 605 | 606 | printf("reading objects\n"); 607 | 608 | // try and read the object 609 | printf("reading object, expect 0x11111111\n\r"); 610 | result = read(2, 4, &testCounter); 611 | if (result == SS_OK) { 612 | printf("test object was found value was %lx\n\r", testCounter); 613 | } else if (result == SS_NOT_FOUND) { 614 | printf("test object not found\n\r"); 615 | } else { 616 | printf("read returned %u\n\r", result); 617 | } 618 | 619 | // try and read the object 620 | printf("reading object, expect 0x22222222\n\r"); 621 | result = read(TEST_ID, 4, &testCounter); 622 | if (result == SS_OK) { 623 | printf("test object was found value was %lx\n\r", testCounter); 624 | } else if (result == SS_NOT_FOUND) { 625 | printf("test object not found\n\r"); 626 | } else { 627 | printf("read returned %u\n\r", result); 628 | } 629 | 630 | 631 | } 632 | -------------------------------------------------------------------------------- /Firmware/src/SimpleStore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Minimalist persistent storage for GD32 5 | * 6 | * Requirements: 7 | * save a small number of data objects to persistent storage 8 | * retrieve a specified data object 9 | * update a specified data object with new values 10 | * delete a specified data object from the store 11 | * detect if the storage space is not valid so that we don't overwrite/erase code 12 | * 13 | * Specifications: 14 | * max ? objects (16 bit id with 2 reserved values puts upper limit at 65534, but there 15 | * won't be enough ram to work with that many) 16 | * max 255 bytes per object 17 | * 18 | * Implementation: 19 | * Underlying capabilities provide page erase to set all bits to 1 and 20 | * write word or half word (no write byte?) via API calls. 21 | * Pages are 1K 22 | * Writes can only clear bits, so reserve 0 as a special marker. 23 | * NB overwriting is only permitted if the new value is all 0, so we can't use read/modify/write to clear 24 | * specific bits. Since the smallest unit for writes is 16 bits and we need to preserve the record length 25 | * when deleting, id will have to have its own 16 field. 26 | * reads can be performed using normal memory access (no api calls needed) 27 | * use a simple eyecatcher in the first word of storage to validate that we're looking at the right memory. 28 | * It's probably safe to auto-init the storage area on first use if the entire space contains 0xFF. This 29 | * would save having to figure out a first-time setup mechanism. 30 | * 31 | * Define an object storage record as: 32 | * 16 bits ID (0 and 0xFFFF reserved) 33 | * 8 bits data length n in bytes 34 | * n bytes data 35 | * 36 | * Each object stored must have a unique ID by which it can be retrieved. ID 0 is reserved 37 | * to indicate the object is no longer in use. ID 0xFFFF is reserved to detect freespace. 38 | * 39 | * Object records are aligned to even addresses. This makes sure that any garbage byte written 40 | * as a result of not having a byte sized write api won't cause problems with the next record. 41 | * 42 | * Finding a specific object or the start of free space will involve traversing the set of 43 | * objects using the data length fields, so these must be preserved even on deleted objects. 44 | * Updating an object will involve setting the ID of the old version to 0 and writing a new 45 | * copy at the end of the list (in the unused space). Unused space will be detected by the 46 | * ID having the reserved value of 0xFFFF (the value after the page is erased). 47 | * When there isn't room to write a new value at the end of the list we will need to collect 48 | * all valid objects into ram, erase the page and then write everything back (a GC of the storage). 49 | * 50 | * 1 page should be enough in the initial instance 51 | * 52 | * Conceptual behaviour (doesn't map directly to the API): 53 | * 54 | * SAVE 55 | * validate the eyecatcher 56 | * if eyecatcher not found, but the storage area is untouched 0xFFs, auto-init 57 | * if eyecatcher not found, and the storage area contains non-0xFF data, then fail the call with appropriate error code. 58 | * Search the storage for the first unused space (requires walking the set of objects using the length field) 59 | * Q. Error check for ID already present? Simpler API would have a unified SAVE/UPDATE function that could handle both cases 60 | * if there is not enough space available for the new object even after GC 61 | * return with error code for storage full 62 | * if there is not enough space to write the new object 63 | * GC 64 | * write the id, length and object data 65 | * 66 | * READ 67 | * validate the eyecatcher 68 | * Search the storage for the specified ID 69 | * read the length 70 | * read the object data 71 | * 72 | * UPDATE 73 | * validate the eyecatcher 74 | * Search the storage for the specified ID (if not found behave as SAVE) 75 | * read the length 76 | * if the length is the same as the new data length 77 | * read the data and compare with new values 78 | * if he data is unchanged, return 79 | * Overwrite the ID with 0 to indicate that the old record is no longer in use 80 | * (Q. postpone until after a successful write of the new data? If we have a failure between operations is it better to 81 | * lose the old data or have a duplicate entry?) 82 | * As per SAVE, to write the new data 83 | * 84 | * DELETE 85 | * validate the eyecatcher 86 | * Search the storage for the specified ID 87 | * if found, overwrite the ID with 0 88 | * 89 | * Garbage Collect (GC) 90 | * validate the eyecatcher 91 | * copy all existing objects into ram 92 | * erase the storage 93 | * write all objects back to flash 94 | * 95 | * Things that need to be decided: 96 | * Are 0 length objects allowed? 97 | */ 98 | 99 | #include 100 | 101 | #define FLASH_PAGE_SIZE 1024 102 | 103 | // Use the last n pages of the address space 104 | #define SS_N_PAGES 1 105 | #define SS_START_ADDRESS (0x08020000 - (SS_N_PAGES * FLASH_PAGE_SIZE)) 106 | 107 | // The first word of the storage space contains a special value so we can validate 108 | // we're looking at the right page 109 | #define SS_EYECATCHER 0x55AA55AA 110 | 111 | // Not actually using this anywhere yet, so treat with suspicion in case I let it get stale 112 | struct SS_Record_s { 113 | uint16_t id; 114 | uint16_t length; 115 | uint8_t data[1]; // the first of length data bytes 116 | }; 117 | typedef SS_Record_s SS_Record_t; 118 | 119 | enum SS_Result { 120 | SS_OK, 121 | SS_LENGTH_MISMATCH, 122 | SS_NOT_FOUND, 123 | SS_NO_SPACE, 124 | SS_MISSING_EYECATCHER, 125 | SS_STORAGE_CORRUPTED 126 | }; 127 | 128 | class SimpleStore 129 | { 130 | private: 131 | static uint8_t _write(const uint16_t id, const uint16_t length, void *data); // internal version of write that doesn't handle updates 132 | static uint32_t gc(); // defragment the storage. Requires a page erase. Called from write(). 133 | static bool storageIsClean(); // return true if the storage area contains only 0xFF in all bytes 134 | 135 | public: 136 | static uint8_t read(const uint16_t id, uint16_t expectedLength, void *data); 137 | static uint8_t write(const uint16_t id, const uint16_t length, void *data); // can do both first write and subsequent updates 138 | static uint8_t remove(const uint16_t id); 139 | 140 | static uint32_t addressOf(const uint16_t id); // return the address of the specified object, or 0 if not found 141 | static uint32_t addressOfFreespace(); 142 | static uint32_t freespaceRemaining(); 143 | static uint32_t sizeofLiveObjects(); // total storage used by live objects 144 | 145 | static void test(); // some basic function tests used during development 146 | }; -------------------------------------------------------------------------------- /Firmware/src/common.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "common.h" 3 | 4 | // TODO: Validate values for RFmodeCycleAddtionalTime and RFmodeCycleInterval for rates lower than 50HZ 5 | 6 | #if defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_FCC_915) || defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433) 7 | 8 | #include "SX127xDriver.h" 9 | extern SX127xDriver Radio; 10 | 11 | expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = { 12 | {0, RATE_200HZ, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_7, 5000, TLM_RATIO_1_64, 2, 8}, 13 | {1, RATE_100HZ, SX127x_BW_500_00_KHZ, SX127x_SF_7, SX127x_CR_4_7, 10000, TLM_RATIO_1_64, 2, 8}, 14 | {2, RATE_50HZ, SX127x_BW_500_00_KHZ, SX127x_SF_8, SX127x_CR_4_7, 20000, TLM_RATIO_NO_TLM, 2, 10}, 15 | {3, RATE_25HZ, SX127x_BW_500_00_KHZ, SX127x_SF_9, SX127x_CR_4_7, 40000, TLM_RATIO_NO_TLM, 2, 10}, 16 | {4, RATE_4HZ, SX127x_BW_500_00_KHZ, SX127x_SF_12, SX127x_CR_4_7, 250000, TLM_RATIO_1_4, 2, 10}}; // for model recovery 17 | 18 | expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = { 19 | {0, RATE_200HZ, -112, 4380, 3500, 2000, 2000, 5000}, // ~ 3 sync packets 20 | {1, RATE_100HZ, -117, 8770, 3500, 4000, 2000, 5000}, 21 | {2, RATE_50HZ, -120, 17540, 3500, 6000, 2000, 5000}, 22 | {3, RATE_25HZ, -123, 17540, 3500, 12000, 2000, 5000}, 23 | {4, RATE_4HZ, -131, 239620, 30000, 60000, 0, 250}}; // this means always send sync on ch[0] as soon as we can 24 | #endif 25 | 26 | #if defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 27 | 28 | #include "SX1280.h" 29 | extern SX1280Driver Radio; 30 | 31 | #ifdef ELRS_OG_COMPATIBILITY 32 | expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = { 33 | {0, RATE_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_4_5, 2000, TLM_RATIO_1_128, 2, 12}, // needs more work/debugging and it's not even supported in OTX yet 34 | {1, RATE_250HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_LI_4_7, 4000, TLM_RATIO_1_64, 2, 14}, 35 | {2, RATE_150HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_7, 6666, TLM_RATIO_1_32, 2, 12}, 36 | {3, RATE_50HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF9, SX1280_LORA_CR_4_5, 20000, TLM_RATIO_NO_TLM, 2, 12}}; 37 | 38 | expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = { 39 | {0, RATE_500HZ, -105, 4380, 3500, 1000, 2000, 5000}, // ~ 3 sync packets 40 | {1, RATE_250HZ, -108, 4380, 3500, 2500, 2000, 5000}, // ~ 3 sync packets 41 | {2, RATE_150HZ, -112, 8770, 3500, 2500, 2000, 5000}, 42 | {3, RATE_50HZ, -117, 17540, 3500, 2500, 2000, 5000}}; // this means always send sync on ch[0] as soon as we can 43 | 44 | #else // not ELRS_OG_COMPATIBILITY 45 | 46 | expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = { 47 | // enum_rate, bw, sf, cr, interval us, TLMinterval, FHSShopInterval, PreambleLen 48 | {0, RATE_1KHZ, SX1280_LORA_BW_1600, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_5, 1000, TLM_RATIO_1_128, 8, 12}, // 1000Hz 49 | // {0, RATE_1KHZ, SX1280_LORA_BW_1600, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_7, 1200, TLM_RATIO_1_128, 8, 12}, // 833Hz, 871us 50 | {1, RATE_800HZ, SX1280_LORA_BW_1600, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_7, 1200, TLM_RATIO_1_128, 8, 12}, // 871us 51 | 52 | 53 | // {1, RATE_800HZ, SX1280_LORA_BW_1600, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_6, 1250, TLM_RATIO_1_128, 8, 12}, // 800Hz 54 | 55 | // {1, RATE_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_4_5, 2000, TLM_RATIO_1_128, 8, 12}, 56 | {2, RATE_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_7, 2100, TLM_RATIO_1_128, 8, 12}, // 476Hz, 1744 us 57 | 58 | // Getting rxfail on indoor test quad, even though this is quicker than CR_4_5 59 | // {1, RATE_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_6, 2000, TLM_RATIO_1_128, 8, 12}, 60 | {3, RATE_250HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_LI_4_7, 4000, TLM_RATIO_1_64, 8, 12}, 61 | {4, RATE_150HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_7, 7692, TLM_RATIO_1_32, 4, 12}, // 130Hz 62 | {5, RATE_50HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF8, SX1280_LORA_CR_LI_4_7,13333, TLM_RATIO_1_32, 2, 12}}; // 75Hz 63 | 64 | expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = { 65 | 66 | // NB RFmodeCycleInterval is used both for the time between switch packet rates when searching for a connection, and also as a 67 | // timeout for deciding the link has failed! 68 | // RFmodeCycleAddtionalTime is used to timeout a connection that can't get out of tentative state 69 | 70 | // rate sens TOA RFmodeCycleInterval RFmodeCycleAddtionalTime SyncPktIntervalDisconnected SyncPktIntervalConnected 71 | // {0, RATE_1KHZ, -99, 753, 500, 1000, 100, 5000}, // with hw crc 72 | 73 | // long rx cycle times 74 | // TODO check the TOA values 75 | {0, RATE_1KHZ, -99, 675, 500, 1000, 100, 5000}, // no hw crc 76 | {1, RATE_800HZ, -99, 675, 500, 1000, 100, 5000}, // TODO needs updating 77 | {2, RATE_500HZ, -105, 1626, 500, 1000, 100, 5000}, 78 | {3, RATE_250HZ, -108, 3567, 1000, 1000, 100, 5000}, 79 | {4, RATE_150HZ, -112, 6660, 1000, 4000, 100, 5000}, // todo, see if the large RFmodeCycleAddtionalTime can be reduced 80 | {5, RATE_50HZ, -120,12059, 1000, 6000, 133, 5000}}; 81 | 82 | #endif // ELRS_OG_COMPATIBILITY 83 | 84 | #endif // defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 85 | 86 | expresslrs_mod_settings_s *get_elrs_airRateConfig(int8_t index); 87 | //const expresslrs_mod_settings_s * ExpressLRS_nextAirRate; 88 | expresslrs_mod_settings_s *ExpressLRS_currAirRate; 89 | expresslrs_mod_settings_s *ExpressLRS_prevAirRate; 90 | 91 | ICACHE_RAM_ATTR expresslrs_mod_settings_s *get_elrs_airRateConfig(int8_t index) 92 | { 93 | // Protect against out of bounds rate 94 | if (index < 0) 95 | { 96 | // Set to first entry in the array (fastest) 97 | return &ExpressLRS_AirRateConfig[0]; 98 | } 99 | else if (index > (RATE_MAX - 1)) 100 | { 101 | // Set to last usable entry in the array (slowest) 102 | return &ExpressLRS_AirRateConfig[RATE_MAX - 1]; 103 | } 104 | return &ExpressLRS_AirRateConfig[index]; 105 | } 106 | 107 | ICACHE_RAM_ATTR expresslrs_rf_pref_params_s *get_elrs_RFperfParams(int8_t index) 108 | { 109 | // Protect against out of bounds rate 110 | if (index < 0) 111 | { 112 | // Set to first entry in the array (200HZ) 113 | return &ExpressLRS_AirRateRFperf[0]; 114 | } 115 | else if (index > (RATE_MAX - 1)) 116 | { 117 | // Set to last usable entry in the array (currently 50HZ) 118 | return &ExpressLRS_AirRateRFperf[RATE_MAX - 1]; 119 | } 120 | return &ExpressLRS_AirRateRFperf[index]; 121 | } 122 | 123 | expresslrs_mod_settings_s *ExpressLRS_currAirRate_Modparams; 124 | expresslrs_rf_pref_params_s *ExpressLRS_currAirRate_RFperfParams; 125 | 126 | //expresslrs_mod_settings_s *ExpressLRS_nextAirRate; 127 | //expresslrs_mod_settings_s *ExpressLRS_prevAirRate; 128 | bool ExpressLRS_AirRateNeedsUpdate = false; 129 | 130 | connectionState_e connectionState = disconnected; 131 | connectionState_e connectionStatePrev = disconnected; 132 | 133 | #ifndef MY_UID 134 | //uint8_t UID[6] = {48, 174, 164, 200, 100, 50}; 135 | //uint8_t UID[6] = {180, 230, 45, 152, 126, 65}; //sandro unique ID 136 | // uint8_t UID[6] = {180, 230, 45, 152, 125, 173}; // Wez's unique ID 137 | #error "Must define MY_UID" 138 | #else 139 | uint8_t UID[6] = {MY_UID}; 140 | #endif 141 | 142 | uint8_t CRCCaesarCipher = UID[4]; 143 | uint8_t DeviceAddr = UID[5] & 0b111111; // temporarily based on uid until listen before assigning method merged 144 | 145 | #define RSSI_FLOOR_NUM_READS 5 // number of times to sweep the noise foor to get avg. RSSI reading 146 | #define MEDIAN_SIZE 20 147 | 148 | uint8_t ICACHE_RAM_ATTR TLMratioEnumToValue(expresslrs_tlm_ratio_e enumval) 149 | { 150 | switch (enumval) 151 | { 152 | case TLM_RATIO_NO_TLM: 153 | return 0; 154 | break; 155 | case TLM_RATIO_1_2: 156 | return 2; 157 | break; 158 | case TLM_RATIO_1_4: 159 | return 4; 160 | break; 161 | case TLM_RATIO_1_8: 162 | return 8; 163 | break; 164 | case TLM_RATIO_1_16: 165 | return 16; 166 | break; 167 | case TLM_RATIO_1_32: 168 | return 32; 169 | break; 170 | case TLM_RATIO_1_64: 171 | return 64; 172 | break; 173 | case TLM_RATIO_1_128: 174 | return 128; 175 | break; 176 | default: 177 | return 0; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Firmware/src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FHSS.h" 4 | #include "config.h" 5 | 6 | #if defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_FCC_915) || defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433) 7 | #include "SX127xDriver.h" 8 | #endif 9 | 10 | #if defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 11 | #include "SX1280Driver.h" 12 | #endif 13 | 14 | // XXX what does this do? 15 | #define One_Bit_Switches 16 | 17 | extern uint8_t UID[6]; 18 | extern uint8_t CRCCaesarCipher; 19 | extern uint8_t DeviceAddr; 20 | 21 | typedef enum 22 | { 23 | TLM_RATIO_NO_TLM = 0, 24 | TLM_RATIO_1_128 = 1, 25 | TLM_RATIO_1_64 = 2, 26 | TLM_RATIO_1_32 = 3, 27 | TLM_RATIO_1_16 = 4, 28 | TLM_RATIO_1_8 = 5, 29 | TLM_RATIO_1_4 = 6, 30 | TLM_RATIO_1_2 = 7, 31 | TLM_RATIO_NUM_VALUES = 8 32 | 33 | } expresslrs_tlm_ratio_e; 34 | 35 | typedef enum 36 | { 37 | bad_sync_retry = 4, 38 | bad_sync = 3, 39 | connected = 2, 40 | tentative = 1, 41 | disconnected = 0 42 | } connectionState_e; 43 | 44 | typedef enum 45 | { 46 | tim_disconnected = 0, 47 | tim_tentative = 1, 48 | tim_locked = 2 49 | } RXtimerState_e; 50 | 51 | extern connectionState_e connectionState; 52 | extern connectionState_e connectionStatePrev; 53 | 54 | typedef enum 55 | { 56 | RF_DOWNLINK_INFO = 0, 57 | RF_UPLINK_INFO = 1, 58 | RF_AIRMODE_PARAMETERS = 2 59 | } expresslrs_tlm_header_e; 60 | 61 | #ifdef ELRS_OG_COMPATIBILITY 62 | typedef enum 63 | { 64 | RATE_500HZ = 0, 65 | RATE_250HZ = 1, 66 | RATE_200HZ = 2, 67 | RATE_150HZ = 3, 68 | RATE_100HZ = 4, 69 | RATE_50HZ = 5, 70 | RATE_25HZ = 6, 71 | RATE_4HZ = 7, 72 | RATE_ENUM_MAX = 8 73 | } expresslrs_RFrates_e; // Max value of 16 since only 4 bits have been assigned in the sync package. 74 | #else 75 | typedef enum 76 | { 77 | RATE_1KHZ = 0, 78 | RATE_800HZ = 1, 79 | RATE_500HZ = 2, 80 | RATE_250HZ = 3, 81 | RATE_200HZ = 4, 82 | RATE_150HZ = 5, 83 | RATE_100HZ = 6, 84 | RATE_50HZ = 7, 85 | RATE_25HZ = 8, 86 | RATE_4HZ = 9 87 | } expresslrs_RFrates_e; // Max value of 16 since only 4 bits have been assigned in the sync package. 88 | #endif // ELRS_OG_COMPATIBILITY 89 | 90 | typedef struct expresslrs_rf_pref_params_s 91 | { 92 | int8_t index; 93 | expresslrs_RFrates_e enum_rate; // Max value of 16 since only 4 bits have been assigned in the sync package. 94 | int32_t RXsensitivity; //expected RF sensitivity based on 95 | uint32_t TOA; //time on air in microseconds 96 | uint32_t RFmodeCycleInterval; 97 | uint32_t RFmodeCycleAddtionalTime; // the time to keep trying in tentative mode before giving up and starting over 98 | uint32_t SyncPktIntervalDisconnected; 99 | uint32_t SyncPktIntervalConnected; 100 | 101 | } expresslrs_rf_pref_params_s; 102 | 103 | #if defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_FCC_915) || defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433) 104 | #define RATE_MAX 5 105 | #define RATE_DEFAULT 0 106 | typedef struct expresslrs_mod_settings_s 107 | { 108 | int8_t index; 109 | expresslrs_RFrates_e enum_rate; // Max value of 16 since only 4 bits have been assigned in the sync package. 110 | SX127x_Bandwidth bw; 111 | SX127x_SpreadingFactor sf; 112 | SX127x_CodingRate cr; 113 | uint32_t interval; //interval in us seconds that corresponds to that frequnecy 114 | expresslrs_tlm_ratio_e TLMinterval; // every X packets is a response TLM packet, should be a power of 2 115 | uint8_t FHSShopInterval; // every X packets we hope to a new frequnecy. Max value of 16 since only 4 bits have been assigned in the sync package. 116 | uint8_t PreambleLen; 117 | 118 | } expresslrs_mod_settings_t; 119 | 120 | #endif 121 | 122 | #if defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 123 | 124 | #ifdef ELRS_OG_COMPATIBILITY 125 | #define RATE_MAX 4 // actually the number of rates, so the max value is RATE_MAX-1 126 | #define RATE_DEFAULT 0 127 | #else 128 | #define RATE_MAX 6 // actually the number of rates, so the max value is RATE_MAX-1 129 | #define RATE_DEFAULT 2 130 | #endif // ELRS_OG_COMPATIBILITY 131 | 132 | typedef struct expresslrs_mod_settings_s 133 | { 134 | uint8_t index; 135 | expresslrs_RFrates_e enum_rate; // Max value of 16 since only 4 bits have been assigned in the sync package. 136 | SX1280_RadioLoRaBandwidths_t bw; 137 | SX1280_RadioLoRaSpreadingFactors_t sf; 138 | SX1280_RadioLoRaCodingRates_t cr; 139 | uint32_t interval; //interval in us seconds that corresponds to that frequnecy 140 | expresslrs_tlm_ratio_e TLMinterval; // every X packets is a response TLM packet, should be a power of 2 141 | uint8_t FHSShopInterval; // every X packets we hope to a new frequnecy. Max value of 16 since only 4 bits have been assigned in the sync package. 142 | uint8_t PreambleLen; 143 | 144 | } expresslrs_mod_settings_t; 145 | 146 | #endif // defined(Regulatory_Domain_ISM_2400) || defined(Regulatory_Domain_ISM_2400_NA) 147 | 148 | expresslrs_mod_settings_s *get_elrs_airRateConfig(int8_t index); 149 | expresslrs_rf_pref_params_s *get_elrs_RFperfParams(int8_t index); 150 | 151 | uint8_t ICACHE_RAM_ATTR TLMratioEnumToValue(expresslrs_tlm_ratio_e enumval); 152 | 153 | extern expresslrs_mod_settings_s *ExpressLRS_currAirRate_Modparams; 154 | extern expresslrs_rf_pref_params_s *ExpressLRS_currAirRate_RFperfParams; 155 | //extern expresslrs_mod_settings_s *ExpressLRS_nextAirRate; 156 | //extern expresslrs_mod_settings_s *ExpressLRS_prevAirRate; 157 | 158 | extern bool ExpressLRS_AirRateNeedsUpdate; 159 | 160 | //ELRS SPECIFIC OTA CRC 161 | #define ELRS_CRC_POLY 0x83 162 | -------------------------------------------------------------------------------- /Firmware/src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // which board are we using 4 | 5 | // #define LONGAN_NANO 6 | // #define T_DISPLAY 7 | #define PCB_V1_0 8 | 9 | // some things are specific to each build 10 | 11 | // #define PROTOTYPE_V3 12 | // #define PROTOTYPE_V4 13 | #define PROTOTYPE_V5 14 | 15 | // testing compat mode with mainstream elrs rx 16 | // TODO make dynamic 17 | #define ELRS_OG_COMPATIBILITY 18 | 19 | #ifdef ELRS_OG_COMPATIBILITY 20 | #define Regulatory_Domain_ISM_2400 21 | #else 22 | #define Regulatory_Domain_ISM_2400_NA 23 | #endif 24 | 25 | // TODO make this runtime dynamic 26 | // define the type of radio module being used 27 | 28 | // #define RADIO_E28_12 // CAREFUL - this will break _20 and _27 if you use it by accident 29 | // #define RADIO_E28_20 30 | #define RADIO_E28_27 31 | 32 | #ifdef RADIO_E28_12 33 | // E28-12 and both GNICERF modules can use the full output range 34 | #define MAX_PRE_PA_POWER 13 35 | #define DISARM_POWER (0) 36 | #elif defined(RADIO_E28_20) 37 | #define MAX_PRE_PA_POWER (-2) 38 | #define DISARM_POWER (-12) 39 | #elif defined(RADIO_E28_27) 40 | #define MAX_PRE_PA_POWER 0 41 | #define DISARM_POWER (-15) 42 | #endif 43 | 44 | 45 | 46 | // how many switches do we have? 47 | // hybrid 8 currently deals with up to 8 switches, but if we only have 4 fitted we can save some time by 48 | // setting the lower value here 49 | #define MAX_SWITCHES 4 50 | 51 | 52 | // definitions for simpler gpio naming scheme 53 | 54 | enum ports { 55 | PORTA, 56 | PORTB, 57 | PORTC 58 | }; 59 | 60 | #define PA1 (PORTA << 16 | GPIO_PIN_1) 61 | #define PA2 (PORTA << 16 | GPIO_PIN_2) 62 | #define PA3 (PORTA << 16 | GPIO_PIN_3) 63 | #define PA4 (PORTA << 16 | GPIO_PIN_4) 64 | #define PA5 (PORTA << 16 | GPIO_PIN_5) 65 | #define PA6 (PORTA << 16 | GPIO_PIN_6) 66 | #define PA7 (PORTA << 16 | GPIO_PIN_7) 67 | #define PA8 (PORTA << 16 | GPIO_PIN_8) 68 | #define PA9 (PORTA << 16 | GPIO_PIN_9) 69 | #define PA10 (PORTA << 16 | GPIO_PIN_10) 70 | #define PA11 (PORTA << 16 | GPIO_PIN_11) 71 | #define PA12 (PORTA << 16 | GPIO_PIN_12) 72 | #define PA13 (PORTA << 16 | GPIO_PIN_13) 73 | #define PA14 (PORTA << 16 | GPIO_PIN_14) 74 | #define PA15 (PORTA << 16 | GPIO_PIN_15) 75 | 76 | #define PB1 (PORTB << 16 | GPIO_PIN_1) 77 | #define PB2 (PORTB << 16 | GPIO_PIN_2) 78 | #define PB3 (PORTB << 16 | GPIO_PIN_3) 79 | #define PB4 (PORTB << 16 | GPIO_PIN_4) 80 | #define PB5 (PORTB << 16 | GPIO_PIN_5) 81 | #define PB6 (PORTB << 16 | GPIO_PIN_6) 82 | #define PB7 (PORTB << 16 | GPIO_PIN_7) 83 | #define PB8 (PORTB << 16 | GPIO_PIN_8) 84 | #define PB9 (PORTB << 16 | GPIO_PIN_9) 85 | #define PB10 (PORTB << 16 | GPIO_PIN_10) 86 | #define PB11 (PORTB << 16 | GPIO_PIN_11) 87 | #define PB12 (PORTB << 16 | GPIO_PIN_12) 88 | #define PB13 (PORTB << 16 | GPIO_PIN_13) 89 | #define PB14 (PORTB << 16 | GPIO_PIN_14) 90 | #define PB15 (PORTB << 16 | GPIO_PIN_15) 91 | 92 | #define PC1 ((PORTC << 16) | GPIO_PIN_1) 93 | #define PC2 ((PORTC << 16) | GPIO_PIN_2) 94 | #define PC3 ((PORTC << 16) | GPIO_PIN_3) 95 | #define PC4 ((PORTC << 16) | GPIO_PIN_4) 96 | #define PC5 ((PORTC << 16) | GPIO_PIN_5) 97 | #define PC6 ((PORTC << 16) | GPIO_PIN_6) 98 | #define PC7 ((PORTC << 16) | GPIO_PIN_7) 99 | #define PC8 ((PORTC << 16) | GPIO_PIN_8) 100 | #define PC9 ((PORTC << 16) | GPIO_PIN_9) 101 | #define PC10 ((PORTC << 16) | GPIO_PIN_10) 102 | #define PC11 ((PORTC << 16) | GPIO_PIN_11) 103 | #define PC12 ((PORTC << 16) | GPIO_PIN_12) 104 | #define PC13 ((PORTC << 16) | GPIO_PIN_13) 105 | #define PC14 ((PORTC << 16) | GPIO_PIN_14) 106 | #define PC15 ((PORTC << 16) | GPIO_PIN_15) 107 | 108 | 109 | #define PIN(x) (x & 0xFFFF) 110 | 111 | // ===================== 112 | 113 | 114 | 115 | 116 | #ifdef LONGAN_NANO 117 | 118 | // #define RADIO_BUSY_PORT GPIOA 119 | // #define RADIO_BUSY_PIN GPIO_PIN_11 120 | 121 | // #define RADIO_RESET_PORT GPIOA 122 | // #define RADIO_RESET_PIN GPIO_PIN_12 123 | 124 | #define RADIO_BUSY_PORT GPIOA 125 | #define RADIO_BUSY_PIN GPIO_PIN_12 126 | 127 | #define RADIO_RESET_PORT GPIOA 128 | #define RADIO_RESET_PIN GPIO_PIN_11 129 | 130 | 131 | // rotary encoder button 132 | // #define RE_BUTTON_PORT GPIOA 133 | // #define RE_BUTTON_PIN GPIO_PIN_4 134 | 135 | #define RE_BUTTON_PORT GPIOC 136 | #define RE_BUTTON_PIN GPIO_PIN_13 137 | 138 | // for testing on the nano 139 | // #define SWB_TMP PA14 140 | 141 | #define SWA_LOW PC15 142 | #define SWA_HIGH PC14 143 | 144 | #define SWB_LOW PA13 145 | #define SWB_HIGH PA14 146 | 147 | #define SWC_LOW PA15 148 | #define SWC_HIGH PB3 149 | 150 | #define SWD_LOW PB5 151 | #define SWD_HIGH PB4 152 | 153 | // need adc scaling constants even if we don't have anything connected 154 | #define ADC_PITCH_REVERSED false 155 | #define ADC_PITCH_MIN 726u 156 | #define ADC_PITCH_CTR 2080u 157 | #define ADC_PITCH_MAX 3497u 158 | 159 | #define ADC_ROLL_REVERSED true 160 | #define ADC_ROLL_MIN 592u 161 | #define ADC_ROLL_CTR 2083u 162 | #define ADC_ROLL_MAX 3547u 163 | 164 | #define ADC_THROTTLE_REVERSED true 165 | #define ADC_THROTTLE_MIN 561u 166 | #define ADC_THROTTLE_MAX 3504u 167 | 168 | #define ADC_YAW_REVERSED false 169 | #define ADC_YAW_MIN 741u 170 | #define ADC_YAW_CTR 2081u 171 | #define ADC_YAW_MAX 3436u 172 | 173 | 174 | #elif defined(T_DISPLAY) 175 | 176 | #define RADIO_BUSY_PORT GPIOA 177 | #define RADIO_BUSY_PIN GPIO_PIN_12 178 | 179 | #define RADIO_RESET_PORT GPIOA 180 | #define RADIO_RESET_PIN GPIO_PIN_11 181 | 182 | #define RADIO_RXEN_PORT GPIOB 183 | #define RADIO_RXEN_PIN GPIO_PIN_11 184 | 185 | #define RADIO_TXEN_PORT GPIOA 186 | #define RADIO_TXEN_PIN GPIO_PIN_8 187 | 188 | // rotary encoder button 189 | #define RE_BUTTON_PORT GPIOC 190 | #define RE_BUTTON_PIN GPIO_PIN_13 191 | 192 | // RC switches 193 | #define SWA_LOW PC15 194 | #define SWA_HIGH PC14 195 | 196 | // #define SWB_TMP PA13 197 | #define SWB_LOW PA13 198 | #define SWB_HIGH PA14 199 | 200 | #define SWC_LOW PA15 201 | #define SWC_HIGH PB3 202 | 203 | #define SWD_LOW PB5 204 | #define SWD_HIGH PB4 205 | 206 | // Gimbal calibration values 207 | #define ADC_PITCH_REVERSED true 208 | #define ADC_PITCH_MIN 913u 209 | #define ADC_PITCH_CTR 2404u 210 | #define ADC_PITCH_MAX 3864u 211 | 212 | #define ADC_ROLL_REVERSED false 213 | #define ADC_ROLL_MIN 57u 214 | #define ADC_ROLL_CTR 1892u 215 | #define ADC_ROLL_MAX 3763u 216 | 217 | #define ADC_THROTTLE_REVERSED false 218 | #define ADC_THROTTLE_MIN 820u 219 | #define ADC_THROTTLE_MAX 3860u 220 | 221 | #define ADC_YAW_REVERSED true 222 | #define ADC_YAW_MIN 343u 223 | #define ADC_YAW_CTR 1838u 224 | #define ADC_YAW_MAX 3435u 225 | 226 | #elif defined(PCB_V1_0) 227 | 228 | #define RADIO_BUSY_PORT GPIOA 229 | #define RADIO_BUSY_PIN GPIO_PIN_12 230 | 231 | #define RADIO_RESET_PORT GPIOA 232 | #define RADIO_RESET_PIN GPIO_PIN_11 233 | 234 | #define RADIO_RXEN_PORT GPIOB 235 | #define RADIO_RXEN_PIN GPIO_PIN_11 236 | 237 | #define RADIO_TXEN_PORT GPIOA 238 | #define RADIO_TXEN_PIN GPIO_PIN_8 239 | 240 | // rotary encoder button 241 | #define RE_BUTTON_PORT GPIOC 242 | #define RE_BUTTON_PIN GPIO_PIN_13 243 | 244 | // RC switches 245 | #define SWA_LOW PA14 246 | #define SWA_HIGH PA13 247 | 248 | #define SWB_LOW PC14 249 | #define SWB_HIGH PC15 250 | 251 | #define SWC_LOW PB3 252 | #define SWC_HIGH PA15 253 | 254 | #define SWD_LOW PB5 255 | #define SWD_HIGH PB4 256 | 257 | // Buzzer 258 | #define GPIO_BUZZER PA4 259 | 260 | // Gimbal calibration values 261 | // These are for the Jumper hall gimbals, the sensors produce less than half the 262 | // output range compared to the FrSky gimbals. If we were using these long term 263 | // the ADCs would need to be reconfigured to give more resolution. 264 | // #define ADC_PITCH_REVERSED false 265 | // #define ADC_PITCH_MIN 744u 266 | // #define ADC_PITCH_CTR 1262u 267 | // #define ADC_PITCH_MAX 1824u 268 | 269 | // #define ADC_ROLL_REVERSED true 270 | // #define ADC_ROLL_MIN 702u 271 | // #define ADC_ROLL_CTR 1207u 272 | // #define ADC_ROLL_MAX 1809u 273 | 274 | // #define ADC_THROTTLE_REVERSED true 275 | // #define ADC_THROTTLE_MIN 742u 276 | // #define ADC_THROTTLE_MAX 1906u 277 | 278 | // #define ADC_YAW_REVERSED false 279 | // #define ADC_YAW_MIN 586u 280 | // #define ADC_YAW_CTR 1220u 281 | // #define ADC_YAW_MAX 1890u 282 | 283 | // frsky M7 in the V5 prototype 284 | 285 | // Define the mapping between ADC channels and AETR (aileron, elevator, throttle, rudder or roll, pitch, throttle, yaw) 286 | #define ADC_A_CH 3 287 | #define ADC_E_CH 2 288 | #define ADC_T_CH 0 289 | #define ADC_R_CH 1 290 | 291 | #define ADC_PITCH_REVERSED false 292 | #define ADC_PITCH_MIN 726u 293 | #define ADC_PITCH_CTR 2080u 294 | #define ADC_PITCH_MAX 3497u 295 | 296 | #define ADC_ROLL_REVERSED true 297 | #define ADC_ROLL_MIN 592u 298 | #define ADC_ROLL_CTR 2083u 299 | #define ADC_ROLL_MAX 3547u 300 | 301 | #define ADC_THROTTLE_REVERSED true 302 | #define ADC_THROTTLE_MIN 561u 303 | // throttle doesn't need a CTR value 304 | #define ADC_THROTTLE_MAX 3504u 305 | 306 | #define ADC_YAW_REVERSED false 307 | #define ADC_YAW_MIN 741u 308 | #define ADC_YAW_CTR 2081u 309 | #define ADC_YAW_MAX 3436u 310 | 311 | 312 | #else 313 | #error "define the board type in config.h" 314 | #endif 315 | 316 | // common to all boards 317 | 318 | #define RADIO_NSS_PORT GPIOB 319 | #define RADIO_NSS_PIN GPIO_PIN_12 320 | 321 | #define RADIO_DIO1_PORT GPIOB 322 | #define RADIO_DIO1_PIN GPIO_PIN_8 323 | 324 | #define RADIO_DIO2_PORT GPIOB 325 | #define RADIO_DIO2_PIN GPIO_PIN_9 326 | 327 | 328 | // Pins with LEDS are being used for other things. 329 | 330 | // #define LED_PIN GPIO_PIN_13 331 | // #define LED_GPIO_PORT GPIOC 332 | // #define LED_GPIO_CLK RCU_GPIOC 333 | 334 | // #define GREEN_LED GPIO_PIN_1 335 | // #define BLUE_LED GPIO_PIN_2 336 | -------------------------------------------------------------------------------- /Firmware/src/systick.c: -------------------------------------------------------------------------------- 1 | /*! 2 | \file systick.c 3 | \brief the systick configuration file 4 | 5 | \version 2019-6-5, V1.0.0, firmware for GD32VF103 6 | */ 7 | 8 | /* 9 | Copyright (c) 2019, GigaDevice Semiconductor Inc. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 3. Neither the name of the copyright holder nor the names of its contributors 20 | may be used to endorse or promote products derived from this software without 21 | specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 32 | OF SUCH DAMAGE. 33 | */ 34 | 35 | #include "gd32vf103.h" 36 | #include "systick.h" 37 | 38 | /** 39 | \brief delay a time in milliseconds 40 | Busy loop for specified time. Don't use if you need anything else to run. 41 | \param[in] count: count in milliseconds 42 | \param[out] none 43 | \retval none 44 | */ 45 | void delay(uint32_t count) 46 | { 47 | uint64_t start_mtime, delta_mtime; 48 | 49 | // Don't start measuring until we see an mtime tick 50 | uint64_t tmp = get_timer_value(); 51 | do { 52 | start_mtime = get_timer_value(); 53 | } while (start_mtime == tmp); 54 | 55 | do { 56 | delta_mtime = get_timer_value() - start_mtime; 57 | } while(delta_mtime <(SystemCoreClock/4000.0 *count )); 58 | } 59 | 60 | 61 | void delayMicros(uint32_t count) 62 | { 63 | uint64_t start_mtime, delta_mtime; 64 | 65 | // Don't start measuring until we see an mtime tick 66 | uint64_t tmp = get_timer_value(); 67 | do { 68 | start_mtime = get_timer_value(); 69 | } while (start_mtime == tmp); 70 | 71 | do { 72 | delta_mtime = get_timer_value() - start_mtime; 73 | } while(delta_mtime <(SystemCoreClock/4000000 *count )); 74 | } 75 | -------------------------------------------------------------------------------- /Firmware/src/utils.cpp: -------------------------------------------------------------------------------- 1 | 2 | extern "C" { 3 | 4 | #include "config.h" 5 | #include "gd32vf103.h" 6 | #include "systick.h" 7 | 8 | /* retarget the C library printf function to the USART 9 | * TODO build up a buffer and send via DMA 10 | */ 11 | int _put_char(int ch) 12 | { 13 | usart_data_transmit(USART0, (uint8_t) ch ); 14 | while ( usart_flag_get(USART0, USART_FLAG_TBE)== RESET){ 15 | } 16 | 17 | return ch; 18 | } 19 | 20 | } // extern C 21 | 22 | // ========================================================== 23 | // C++ functions 24 | 25 | #include "utils.h" 26 | 27 | // get_timer_value() runs at main clock / 4, so with 108MHz clock, 28 | // it gives 27MHz ticks 29 | #define TIMER_TO_MILLIS (27000) 30 | #define TIMER_TO_MICROS (27) 31 | 32 | unsigned long millis(void){ 33 | return (unsigned long)(get_timer_value() / TIMER_TO_MILLIS); 34 | } 35 | 36 | unsigned long micros(void){ 37 | return (unsigned long)(get_timer_value() / TIMER_TO_MICROS); 38 | } 39 | 40 | /** Send 8 bit data over SPI and read 8 bit return value 41 | * TODO Not currently used - remove? 42 | */ 43 | uint8_t spi1_transfer(uint8_t x) 44 | { 45 | gpio_bit_reset(RADIO_NSS_PORT, RADIO_NSS_PIN); // set NSS low 46 | 47 | while (0 == spi_i2s_flag_get(SPI1, SPI_FLAG_TBE)) 48 | ; // wait for any previous tx to complete 49 | 50 | spi_i2s_data_transmit(SPI1, x); 51 | 52 | while (0 == spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE)) 53 | ; // wait for rx data to be ready 54 | 55 | uint16_t r = spi_i2s_data_receive(SPI1); 56 | 57 | gpio_bit_set(RADIO_NSS_PORT, RADIO_NSS_PIN); // set NSS high 58 | 59 | return (uint8_t)r; 60 | } 61 | 62 | /** 63 | * Transfer multiple bytes over SPI 64 | * nBytes of data from buffer will be sent, with return values overwriting 65 | * the original data. 66 | * 67 | * TODO replace with DMA 68 | * 69 | * This is the critical path for communicating with the radio 70 | * 71 | */ 72 | void spi1_transferBytes(uint8_t *buffer, const int nBytes) 73 | { 74 | gpio_bit_reset(RADIO_NSS_PORT, RADIO_NSS_PIN); // set NSS low 75 | 76 | for(int i=0; i> 16; 113 | } 114 | 115 | void rngSeed(long newSeed) 116 | { 117 | seed = newSeed; 118 | } 119 | 120 | // returns 0 <= x < max where max <= 256 121 | // (actual upper limit is higher, but there is one and I haven't 122 | // thought carefully about what it is) 123 | unsigned int rngN(unsigned int max) 124 | { 125 | unsigned long x = rng(); 126 | unsigned int result = (x * max) / RNG_MAX; 127 | return result; 128 | } 129 | 130 | // // 0..255 returned 131 | // long rng8Bit(void) 132 | // { 133 | // return rng() & 0b11111111; 134 | // } 135 | 136 | // // 0..31 returned 137 | // long rng5Bit(void) 138 | // { 139 | // return rng() & 0b11111; 140 | // } 141 | 142 | // // 0..2 returned 143 | // long rng0to2(void) 144 | // { 145 | // int randomNumber = rng() & 0b11; 146 | 147 | // while(randomNumber == 3) { 148 | // randomNumber = rng() & 0b11; 149 | // } 150 | // return randomNumber; 151 | // } 152 | -------------------------------------------------------------------------------- /Firmware/src/utils.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | 5 | 6 | 7 | unsigned long millis(void); 8 | unsigned long micros(void); 9 | uint8_t spi1_transfer(uint8_t x); 10 | void spi1_transferBytes(uint8_t *buffer, const int nBytes); 11 | 12 | // random number generator 13 | 14 | #define RNG_MAX 0x7FFF 15 | 16 | long rng(void); 17 | void rngSeed(long newSeed); 18 | unsigned int rngN(unsigned int max); 19 | 20 | // CRSF compatible crc. 21 | // TODO Add a better crsf for elrs ota 22 | 23 | /* CRC8 implementation with polynom = x​7​+ x​6​+ x​4​+ x​2​+ x​0 ​(0xD5) */ 24 | static const unsigned char crc8tab[256] = { 25 | 0x00, 0xD5, 0x7F, 0xAA, 0xFE, 0x2B, 0x81, 0x54, 0x29, 0xFC, 0x56, 0x83, 0xD7, 0x02, 0xA8, 0x7D, 26 | 0x52, 0x87, 0x2D, 0xF8, 0xAC, 0x79, 0xD3, 0x06, 0x7B, 0xAE, 0x04, 0xD1, 0x85, 0x50, 0xFA, 0x2F, 27 | 0xA4, 0x71, 0xDB, 0x0E, 0x5A, 0x8F, 0x25, 0xF0, 0x8D, 0x58, 0xF2, 0x27, 0x73, 0xA6, 0x0C, 0xD9, 28 | 0xF6, 0x23, 0x89, 0x5C, 0x08, 0xDD, 0x77, 0xA2, 0xDF, 0x0A, 0xA0, 0x75, 0x21, 0xF4, 0x5E, 0x8B, 29 | 0x9D, 0x48, 0xE2, 0x37, 0x63, 0xB6, 0x1C, 0xC9, 0xB4, 0x61, 0xCB, 0x1E, 0x4A, 0x9F, 0x35, 0xE0, 30 | 0xCF, 0x1A, 0xB0, 0x65, 0x31, 0xE4, 0x4E, 0x9B, 0xE6, 0x33, 0x99, 0x4C, 0x18, 0xCD, 0x67, 0xB2, 31 | 0x39, 0xEC, 0x46, 0x93, 0xC7, 0x12, 0xB8, 0x6D, 0x10, 0xC5, 0x6F, 0xBA, 0xEE, 0x3B, 0x91, 0x44, 32 | 0x6B, 0xBE, 0x14, 0xC1, 0x95, 0x40, 0xEA, 0x3F, 0x42, 0x97, 0x3D, 0xE8, 0xBC, 0x69, 0xC3, 0x16, 33 | 0xEF, 0x3A, 0x90, 0x45, 0x11, 0xC4, 0x6E, 0xBB, 0xC6, 0x13, 0xB9, 0x6C, 0x38, 0xED, 0x47, 0x92, 34 | 0xBD, 0x68, 0xC2, 0x17, 0x43, 0x96, 0x3C, 0xE9, 0x94, 0x41, 0xEB, 0x3E, 0x6A, 0xBF, 0x15, 0xC0, 35 | 0x4B, 0x9E, 0x34, 0xE1, 0xB5, 0x60, 0xCA, 0x1F, 0x62, 0xB7, 0x1D, 0xC8, 0x9C, 0x49, 0xE3, 0x36, 36 | 0x19, 0xCC, 0x66, 0xB3, 0xE7, 0x32, 0x98, 0x4D, 0x30, 0xE5, 0x4F, 0x9A, 0xCE, 0x1B, 0xB1, 0x64, 37 | 0x72, 0xA7, 0x0D, 0xD8, 0x8C, 0x59, 0xF3, 0x26, 0x5B, 0x8E, 0x24, 0xF1, 0xA5, 0x70, 0xDA, 0x0F, 38 | 0x20, 0xF5, 0x5F, 0x8A, 0xDE, 0x0B, 0xA1, 0x74, 0x09, 0xDC, 0x76, 0xA3, 0xF7, 0x22, 0x88, 0x5D, 39 | 0xD6, 0x03, 0xA9, 0x7C, 0x28, 0xFD, 0x57, 0x82, 0xFF, 0x2A, 0x80, 0x55, 0x01, 0xD4, 0x7E, 0xAB, 40 | 0x84, 0x51, 0xFB, 0x2E, 0x7A, 0xAF, 0x05, 0xD0, 0xAD, 0x78, 0xD2, 0x07, 0x53, 0x86, 0x2C, 0xF9}; 41 | 42 | uint8_t CalcCRC(volatile uint8_t *data, int length); 43 | 44 | -------------------------------------------------------------------------------- /Firmware/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /PCB Gerbers/ExLRS_Filter_Board.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/PCB Gerbers/ExLRS_Filter_Board.zip -------------------------------------------------------------------------------- /PCB Gerbers/ExLRS_Handset_Main_PCB.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/PCB Gerbers/ExLRS_Handset_Main_PCB.zip -------------------------------------------------------------------------------- /PCB Gerbers/RF_Carrier.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/PCB Gerbers/RF_Carrier.zip -------------------------------------------------------------------------------- /Wiki/3D Printing/3D Printing Notes.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/3D Printing/3D Printing Notes.docx -------------------------------------------------------------------------------- /Wiki/BOM - Bill of Materials/ELRS Handset Component List.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/BOM - Bill of Materials/ELRS Handset Component List.xlsx -------------------------------------------------------------------------------- /Wiki/BOM - Bill of Materials/ExLRS Handset BOM - AliExpress.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/BOM - Bill of Materials/ExLRS Handset BOM - AliExpress.docx -------------------------------------------------------------------------------- /Wiki/BOM - Bill of Materials/ExLRS Handset BOM - Digi-Key.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/BOM - Bill of Materials/ExLRS Handset BOM - Digi-Key.xlsx -------------------------------------------------------------------------------- /Wiki/Encoder/ExLRS Handset Encoder.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Encoder/ExLRS Handset Encoder.docx -------------------------------------------------------------------------------- /Wiki/Filter Boards/20210325_175824414_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Filter Boards/20210325_175824414_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Filter Boards/20210325_214350122_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Filter Boards/20210325_214350122_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Filter Boards/20210325_214425201_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Filter Boards/20210325_214425201_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Filter Boards/20210325_224344439_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Filter Boards/20210325_224344439_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Filter Boards/ELRS Handset Filter Boards.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Filter Boards/ELRS Handset Filter Boards.docx -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210325_010320095_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210325_010320095_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210325_175546467_iOS.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210325_175546467_iOS.heic -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210325_175743894_iOS.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210325_175743894_iOS.heic -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210327_000439400_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210327_000439400_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210327_000455407_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210327_000455407_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210327_000506065_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210327_000506065_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/20210327_000602607_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/20210327_000602607_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Main Board Assembly/Main PCB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Main Board Assembly/Main PCB.jpg -------------------------------------------------------------------------------- /Wiki/Micro Controller Unit/20210327_000439400_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Micro Controller Unit/20210327_000439400_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Micro Controller Unit/Micro Controller Unit.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Micro Controller Unit/Micro Controller Unit.docx -------------------------------------------------------------------------------- /Wiki/Platform IO Linux Instructions/20210402_213124126_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Platform IO Linux Instructions/20210402_213124126_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Platform IO Linux Instructions/Platform IO Notes.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Platform IO Linux Instructions/Platform IO Notes.docx -------------------------------------------------------------------------------- /Wiki/RF Boards/20210402_183141254_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/20210402_183141254_iOS.jpg -------------------------------------------------------------------------------- /Wiki/RF Boards/20210402_183323582_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/20210402_183323582_iOS.jpg -------------------------------------------------------------------------------- /Wiki/RF Boards/20210402_183400175_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/20210402_183400175_iOS.jpg -------------------------------------------------------------------------------- /Wiki/RF Boards/20210402_183413119_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/20210402_183413119_iOS.jpg -------------------------------------------------------------------------------- /Wiki/RF Boards/20210402_183540130_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/20210402_183540130_iOS.jpg -------------------------------------------------------------------------------- /Wiki/RF Boards/RF Board Notes.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/RF Boards/RF Board Notes.docx -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210328_185023077_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210328_185023077_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210328_185138206_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210328_185138206_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210328_185158322_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210328_185158322_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210328_185213217_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210328_185213217_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210328_190506651_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210328_190506651_iOS.jpg -------------------------------------------------------------------------------- /Wiki/Wireless Charging Cradle/20210410_000010165_iOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/Wireless Charging Cradle/20210410_000010165_iOS.jpg -------------------------------------------------------------------------------- /Wiki/images/filter_cheat_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/filter_cheat_sheet.png -------------------------------------------------------------------------------- /Wiki/images/pcbs/filter_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/filter_bottom.jpg -------------------------------------------------------------------------------- /Wiki/images/pcbs/filter_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/filter_top.jpg -------------------------------------------------------------------------------- /Wiki/images/pcbs/main_component_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/main_component_side.jpg -------------------------------------------------------------------------------- /Wiki/images/pcbs/main_lcd_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/main_lcd_side.jpg -------------------------------------------------------------------------------- /Wiki/images/pcbs/rc_adapter_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/rc_adapter_bottom.jpg -------------------------------------------------------------------------------- /Wiki/images/pcbs/rc_adapter_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExpressLRS/Handset/d21702dca6575f5b96e8cf38694b0c033f6f22fb/Wiki/images/pcbs/rc_adapter_top.jpg -------------------------------------------------------------------------------- /Wiki/notes/Compiling-for-RISC-V.md: -------------------------------------------------------------------------------- 1 | The current version of the handset uses the TTGO T-Display-GD32 RISC-V development board. Unfortunately since this is a relatively new architecture there are some rough edges in the support for it. This page documents the known issues and work-arounds. 2 | 3 | 1) Conflict with C++ bool in header file 4 | 5 | Problem: 6 | 7 | ``` 8 | /home/testuser/.platformio/packages/framework-gd32vf103-sdk/GD32VF103_standard_peripheral/gd32vf103.h:179:41: error: redeclaration of C++ built-in type 'bool' [-fpermissive] 9 | 179 | typedef enum {FALSE = 0, TRUE = !FALSE} bool; 10 | | ^~~~ 11 | ``` 12 | 13 | Cause: 14 | 15 | The header file from the gd32 sdk isn't fully C++ compatible yet 16 | 17 | Fix: 18 | 19 | Comment out line 179 in .platformio/packages/framework-gd32vf103-sdk/GD32VF103_standard_peripheral/gd32vf103.h 20 | 21 | ``` 22 | // typedef enum {FALSE = 0, TRUE = !FALSE} bool; 23 | ``` 24 | 25 | see: https://github.com/riscv-mcu/GD32VF103_Firmware_Library/issues/1 26 | 27 | 2) Build fails with missing dfu-suffix tool 28 | 29 | (Looks like this may have been fixed in recent platformio versions) 30 | 31 | Problem: 32 | 33 | ``` 34 | Adding dfu suffix to firmware.bin 35 | sh: 1: bin/dfu-suffix: not found 36 | ``` 37 | 38 | Cause: 39 | 40 | dfu tools don't get automatically installed by platformio. 41 | 42 | Fix: 43 | 44 | Download dfu-utils from https://bintray.com/platformio/tool-packages (use search for tool-dfu to find the right package for your operating system). Extract the archive and copy it to (for linux) 45 | 46 | $HOME/.platformio/packages/tool-dfuutil 47 | 48 | see https://community.platformio.org/t/dfu-suffix-not-found-for-longan-nano-build/16710/4 49 | 50 | 3) dfu-util: Cannot open DFU device 28e9:0189 51 | 52 | On Linux, indicates a need to add a rule to udev. Either create a new file in /etc/udev/rules.d or add to an existing one if you already have stuff for other dfu (e.g. betaflight FC controllers) 53 | 54 | ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", MODE="0664", GROUP="whichever group you want to use for dfu access" 55 | 56 | I just use my main userid group for access rather than going to the trouble of adding a special one for dfu. 57 | 58 | You may need to run 59 | ``` 60 | udevadm control --reload-rules 61 | udevadm trigger 62 | ``` 63 | as root after updating the rules for them to take effect. 64 | -------------------------------------------------------------------------------- /Wiki/notes/Filter-Module.md: -------------------------------------------------------------------------------- 1 | ![filter top view](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/filter_top.jpg) 2 | 3 | The filter module filters and buffers the signals from the gimbals before they are sampled by the ADC. 4 | 5 | The project is on EasyEDA at https://easyeda.com/jbk1/expresscontroller 6 | 7 | The circuit is from https://www.electronics-tutorials.ws/filter/second-order-filters.html 8 | 9 | The cutoff frequency to use depends on both personal preference and how noisy the gimbals are. I'm currently using a cutoff of 339Hz which seems to work well, and plan to experiment with some higher frequency versions. The main purpose of having the filter on a module is to make it easy to test different cutoff frequencies. 10 | 11 | 339Hz can be achieved using 10k resistors and 47nF capacitors (or 15k with 33nF is similar at 322Hz). The footprints are for 0805 sized parts. For other cutoff frequencies there is a table of suggested values in the EasyEDA project description. 12 | 13 | The pcb includes pads for changing Q using a resistor divider network in the feedback loop. I'm not currently using this, so the divider resistors on the back of the pcb can be left unpopulated. The ones on the front are still needed to complete the feedback circuit, but the value is not critical. I used 10k which would be a reasonable value if we wanted to modify Q later by adding the resistors on the back. 14 | 15 | There are footprints for power supply decoupling capacitors on the back of the PCB, two 1210 and two 0805. The power rail is 5V so I'd recommend a 10V minimum rating on these capacitors. The larger ones can be anything in the 10 to 100 uF range, the smaller ones in the range 10 to 100 nF, ideally a mix of different values. The footprints don't have the polarity marked, so if using tantalum take extra care to get them the right way around! (but ceramics are cheaper and work fine here). 16 | 17 | There is an assembly "cheat sheet" to help with where each component goes during assembly 18 | 19 | ![build cheat sheet](https://github.com/JBKingdon/Express_CX/blob/master/images/filter_cheat_sheet.png) 20 | 21 | Rear view of the filter 22 | ![underside of filter](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/filter_bottom.jpg) -------------------------------------------------------------------------------- /Wiki/notes/Main-PCB.md: -------------------------------------------------------------------------------- 1 | With all the interesting stuff on modules, the main PCB is mostly just filtering and plugs. The V1.0 PCB pictured has several mistakes on the silk screen - those will get fixed in future revisions. 2 | 3 | ![Main pcb component side](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/main_component_side.jpg) 4 | 5 | ![Main pcb mcu side](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/main_lcd_side.jpg) 6 | 7 | The MCU used is a TTGO GD32V Risc-V board (http://www.lilygo.cn/prod_view.aspx?TypeId=50033&Id=1251). It comes with a good quality LCD and the GD32V processor has good ADC performance for sampling the gimbals at high frequency. 8 | 9 | TODO - more info and build instructions -------------------------------------------------------------------------------- /Wiki/notes/Radio-adapter-PCB.md: -------------------------------------------------------------------------------- 1 | ![Radio adapter top view](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/rc_adapter_top.jpg) 2 | 3 | The radio module is mounted on an adapter pcb to make it easy to swap different versions. Different modules can be used via different adapters, but at the moment the E28 modules are recommended. They are available in 12, 20 and 27 dBm versions and any of these can be used with the same adapter. 4 | 5 | The adapter is very straightforward to build, it has a simple 3v3 reg and filter capacitors on the back, and just the E28 on the front. 6 | 7 | Don't forget to reposition the 0 ohm resistor on the E28 to use the UFL! 8 | 9 | ![Radio adapter bottom view](https://github.com/JBKingdon/Express_CX/blob/master/images/pcbs/rc_adapter_bottom.jpg) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | The Express LRS Handset project is first and foremost an **experimental test bed** for pushing the envelope with what can be done with the ELRS radio link. This handset will not replace your "daily driver", nor will it ever support radio protocols other than ELRS. The Micro Controller Unit (MCU) does not run OpenTX. The user interface is very rudimentary - simple text menus that are selected with a rotary encoder. The firmware for this handset will never achieve "Release Candidate", let alone "Stable" status or be assigned a version number. The software will be a continuous work in progress. 3 | 4 | You will have to order and print parts, assemble, and solder this handset by hand as well as know how to build the firmware from source code and upload to the MCU. 5 | 6 | There will be pain. There will be gnashing of teeth. At this point is where we give you your first and final warning as passed down by ancient sailors: 7 | 8 | *********************************************************** 9 | **Beyond this line there be Dragons . . . .** 10 | *********************************************************** 11 | 12 | OK, now that we've set some expectations for this project we'll list some of the cool features of this handset and explain why you would want to go thru all of the effort to build instead of just purchasing a commercial handset. 13 | 14 | 1. Fly at the fastest and most consistent packet rate available. Currently this is 500Hz, but this handset is capable of much faster. Since the MCU doesn't have to deal with OpenTX, all of its CPU cycles are used to provide the most consistent ELRS signal to the receiver and thus your flight controller. This is the main goal of this project, to increase the performance and stability of the ELRS radio link. With this handset, you'll be right on the bleeding edge with the devs. 15 | 2. Uses FrSky M10 Hall Effect gimbals. If you want the best radio signal, you start with the best data. 16 | 3. Hardware Filtering of gimbal data. Select a level of hardware filtering by swapping out custom daughterboards to fine tune your feel. 17 | 4. Swappable RF deck. RF deck is on a replaceable daughterboard so you can swap from 2.4GHz to 900MHz bands. (900 MHz modules currently not developed) Can also swap to a lower powered RF module to save battery life if you don't need the higher power settings. 18 | 5. 1S2P 18650 LiPo battery. Entire handset sips power from 2 LiPos in parallel. Fly an entire weekend without charging. 19 | 6. Wireless Qi charging. Build a charging cradle that will wirelessly charge your handset when you're not flying. 20 | 7. Internal Moxon antenna. Transmit antenna is mounted internally for aesthetics, but can be mounted externally simply by drilling a hole. 21 | 8. Simplified operation. Quad fliers rarely use 99% of the mixing functions of OpenTX since all of that is done in the Flight Controller. 22 | 9. Parts should be less than $100 U.S. 23 | 10. Did I mention wireless Qi charging? 24 | 25 | At this time the best source of information on this project is our OneDrive folder: 26 | 27 | https://1drv.ms/u/s!Ap0n6SxbLP9mspxu-dxmtthcFGTxvA?e=23NiK6 28 | 29 | If you've read this far... 30 | 31 | ![image](https://github.com/ExpressLRS/Handset/blob/master/images/FNW-case.jpg) 32 | --------------------------------------------------------------------------------