├── 3dprint ├── FM_Transmitter.fcstd └── FM_Transmitter.stl ├── LICENSE ├── README.md ├── documentation ├── FM_Transmitter_pic1.jpg ├── FM_Transmitter_pic2.jpg ├── FM_Transmitter_pic3.jpg ├── FM_Transmitter_pic4.jpg ├── FM_Transmitter_pic5.jpg ├── FM_Transmitter_pic6.jpg ├── FM_Transmitter_pic7.jpg ├── FM_Transmitter_wiring.png └── KT0803K_datasheet.pdf ├── hardware ├── FM_Transmitter_BOM.tsv ├── FM_Transmitter_gerber.zip └── FM_Transmitter_schematic.pdf └── software └── fm_transmitter ├── bin ├── fm_transmitter.bin └── fm_transmitter.hex ├── ld └── ch32v003.ld ├── makefile ├── platformio.ini └── src ├── ch32v003.h ├── gpio.h ├── i2c_tx.c ├── i2c_tx.h ├── kt0803.c ├── kt0803.h ├── main.c ├── ssd1306_txt.c ├── ssd1306_txt.h ├── system.c └── system.h /3dprint/FM_Transmitter.fcstd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/3dprint/FM_Transmitter.fcstd -------------------------------------------------------------------------------- /3dprint/FM_Transmitter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/3dprint/FM_Transmitter.stl -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. 2 | To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send 3 | a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CH32V003 Stereo FM Transmitter 2 | With the portable Li-Ion battery powered Stereo FM Transmitter you can transmit audio of any kind. Simply plug your audio source into the 3.5mm audio jack, set the desired frequency, and then tune any standard FM radio receiver to receive the signal. This device is equipped with cost-effective components, including a CH32V003J4M6 32-bit RISC-V microcontroller, a KT0803K/L transmitter IC, a 128x32 pixel OLED display (SSD1306), and a Li-Ion battery charger (TP4054). 3 | 4 | ![FM_Transmitter_pic1.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic1.jpg) 5 | 6 | # Hardware 7 | ## Schematic 8 | ![FM_Transmitter_wiring.png](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_wiring.png) 9 | 10 | ## The CH32V003 Family of 32-bit RISC-V Microcontrollers 11 | The CH32V003 series is a collection of industrial-grade general-purpose microcontrollers that utilize the QingKe RISC-V2A core design supporting the RV32EC instruction set. These microcontrollers are equipped with various features such as a 48MHz system main frequency, 16KB flash, 2KB SRAM, 2.7V - 5.5V supply voltage support, a single-wire serial debug interface, low power consumption, and an ultra-small package. Additionally, the CH32V003 series includes a built-in set of components including a DMA controller, a 10-bit ADC, op-amp comparators, multiple timers, and standard communication interfaces such as USART, I2C, and SPI. 12 | 13 | ## KT0803K or KT0803L Radio-Station-on-a-Chip 14 | The KT0803K/L is a low cost Monolithic Digital Stereo FM Transmitter, designed to process high-fidelity stereo audio signal and transmit modulated FM signal over a short range. The KT0803K/L features dual 20-bit ΔΣ audio ADCs, a high-fidelity digital stereo audio processor and a fully integrated radio frequency (RF) transmitter. An on-chip low-drop-out regulator (LDO) allows the chip to be integrated in a wide range of low-voltage battery-operated systems with power supply ranging from 1.6V to 3.6V. The KT0803K/L can be controlled via its I²C interface. 15 | 16 | ## TP4054 Li-Ion Battery Charge IC 17 | The TP4054 is a complete constant-current/constant-voltage linear charger for single cell lithium-ion batteries. Its small package and low external component count make the TP4054 ideally suited for portable applications. 18 | 19 | ## ME6209 3.3V Linear Voltage Regulator 20 | The ME6209 series are a group of positive voltage output, three–pin regulator, that provide a high current (max 250mA) even when the input/output voltage differential is small (80mV dropout voltage). Low power consumption (3µA quiescent current) and high accuracy (+/-2%) is achieved through CMOS technology. They allow input voltages as high as 18V. 21 | 22 | ## SSD1306 OLED Display Module 23 | A low-cost SSD1306 4-pin I2C 128x32 pixels 0.91-inch OLED module is used as the display device. Make sure to acquire one with the correct pinout! 24 | 25 | ## Building Instructions 26 | 1. Take the Gerber files (the *zip* file inside the *hardware* folder) and upload them to a PCB (printed circuit board) manufacturer of your choice (e.g., [JLCPCB](https://jlcpcb.com/)). They will use these files to create the circuit board for your device and send it to you. 27 | 2. Once you have the PCB, you can start soldering the components onto it. Use the BOM (bill of materials) and schematic as a guide to make sure everything is connected correctly. You can find the corresponding files in the *hardware* folder. Remove the plastic part from the pin header of the OLED, trim the pins, and solder the OLED module flush onto the PCB. Solder the wire antenna to the corresponding pad on the board. A 75cm (30" = λ / 4) long 28AWG flexible silicone insulated wire works very well. 28 | 29 | ![FM_Transmitter_pic4.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic4.jpg) 30 | 31 | 3. Upload the firmware by following the instructions in the next section (see below). 32 | 4. To create the case for your device, use the *stl* files in the *3dprint* folder with your 3D printer. Glue the battery into the case with double-sided tape. Thread the wire antenna through the small hole in the housing. 33 | 34 | ![FM_Transmitter_pic5.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic5.jpg) 35 | 36 | 5. Connect the battery to the JST connector on the board. Pay attention to the correct polarity, unfortunately there is no standard here! Place the board on the case and screw it with four M2x5mm self-tapping screws. 37 | 38 | ![FM_Transmitter_pic6.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic6.jpg) 39 | 40 | # Software 41 | ## Programming and Debugging Device 42 | To program the CH32V003 microcontroller, you will need a special programming device which utilizes the proprietary single-wire serial debug interface (SDI). The [WCH-LinkE](http://www.wch-ic.com/products/WCH-Link.html) (pay attention to the "E" in the name) is a suitable device for this purpose and can be purchased commercially for around $4. This debugging tool is not only compatible with the CH32V003 but also with other WCH RISC-V and ARM-based microcontrollers. 43 | 44 | ![CH32V003_wch-linke.jpg](https://raw.githubusercontent.com/wagiminator/Development-Boards/main/CH32V003F4P6_DevBoard/documentation/CH32V003_wch-linke.jpg) 45 | 46 | To use the WCH-LinkE on Linux, you need to grant access permissions beforehand by executing the following commands: 47 | ``` 48 | echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8010", MODE="666"' | sudo tee /etc/udev/rules.d/99-WCH-LinkE.rules 49 | echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8012", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-WCH-LinkE.rules 50 | sudo udevadm control --reload-rules 51 | ``` 52 | 53 | On Windows, if you need to you can install the WinUSB driver over the WCH interface 1 using the [Zadig](https://zadig.akeo.ie/) tool. 54 | 55 | To upload the firmware, you need to ensure that the FM Transmitter is switched off or the battery is removed. Then, you should make the following connections to the WCH-LinkE: 56 | 57 | ``` 58 | WCH-LinkE FM Transmitter 59 | +-------+ +-------+ 60 | | SWDIO| <--> |DIO | 61 | | GND| ---> |GND | 62 | | 3V3| ---> |3V3 | 63 | +-------+ +-------+ 64 | ``` 65 | 66 | If the blue LED on the WCH-LinkE remains illuminated once it is connected to the USB port, it means that the device is currently in ARM mode and must be switched to RISC-V mode initially. There are a few ways to accomplish this: 67 | - You can utilize the Python command-line tool [rvprog](https://pypi.org/project/rvprog/) (with *-v* option). 68 | - Alternatively, you can select "WCH-LinkRV" in the software provided by WCH, such as MounRiver Studio or WCH-LinkUtility. 69 | - Another option is to hold down the ModeS button on the device while plugging it into the USB port. 70 | 71 | More information can be found in the [WCH-Link User Manual](http://www.wch-ic.com/downloads/WCH-LinkUserManual_PDF.html). 72 | 73 | ## Compiling and Uploading Firmware using the Makefile 74 | ### Linux 75 | Install the toolchain (GCC compiler, Python3, and rvprog): 76 | ``` 77 | sudo apt install build-essential libnewlib-dev gcc-riscv64-unknown-elf 78 | sudo apt install python3 python3-pip 79 | pip install rvprog 80 | ``` 81 | 82 | Switch off the FM Transmitter or remove the battery. Connect the FM Transmitter via the 3-pin PROG header to the WCH-LinkE programming device. Open a terminal and navigate to the folder with the *makefile*. Run the following command to compile and upload: 83 | ``` 84 | make flash 85 | ``` 86 | 87 | ### Other Operating Systems 88 | Follow the instructions on [CNLohr's ch32v003fun page](https://github.com/cnlohr/ch32v003fun/wiki/Installation) to set up the toolchain on your respective operating system (for Windows, use WSL). Also, install [Python3](https://www.pythontutorial.net/getting-started/install-python/) and [rvprog](https://pypi.org/project/rvprog/). Compile and upload with "make flash". Note that I only have Debian-based Linux and have not tested it on other operating systems. 89 | 90 | ## Compiling and Uploading Firmware using PlatformIO 91 | - Install [PlatformIO](https://platformio.org) and [platform-ch32v](https://github.com/Community-PIO-CH32V/platform-ch32v). Follow [these instructions](https://pio-ch32v.readthedocs.io/en/latest/installation.html) to do so. Linux/Mac users may also need to install [pyenv](https://realpython.com/intro-to-pyenv). 92 | - Click on "Open Project" and select the firmware folder with the *platformio.ini* file. 93 | - Switch off the FM Transmitter or remove the battery. Connect the FM Transmitter via the 3-pin PROG header to the WCH-LinkE programming device. Then click "Upload". 94 | 95 | ## Uploading pre-compiled Firmware Binary 96 | WCH offers the free but closed-source software [WCH-LinkUtility](https://www.wch.cn/downloads/WCH-LinkUtility_ZIP.html) to upload the precompiled hex-file with Windows. Select the "WCH-LinkRV" mode in the software, open the *fm_transmitter.hex* file in the *bin* folder and upload it to the microcontroller. 97 | 98 | Alternatively, there is an open-source tool called [minichlink](https://github.com/cnlohr/ch32v003fun/tree/master/minichlink) developed by Charles Lohr (CNLohr). It can be used with Windows, Linux and Mac. 99 | 100 | If you have installed [Python3](https://www.pythontutorial.net/getting-started/install-python/) on your system, you can also use the platform-independent open-source command-line tool [rvprog](https://pypi.org/project/rvprog/) for uploading: 101 | ``` 102 | rvprog -f bin/fm_transmitter.bin 103 | ``` 104 | 105 | # Operating Instructions 106 | 1. Make sure that the wire antenna is laid as straight as possible horizontally or vertically. 107 | 2. Turn on the transmitter using the power switch. 108 | 3. Use the OK key to switch between transmitter frequency and audio gain display/control mode. 109 | 4. Use the UP or DOWN key to increase/decrease frequency/gain. 110 | 5. If the battery is weak, recharge it via the USB-C port. 111 | 112 | ![FM_Transmitter_pic7.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic7.jpg) 113 | 114 | If you don't have an FM radio receiver and/or you want to build one yourself, take a look [here](https://github.com/wagiminator/CH32V003-FM-Receiver). 115 | 116 | # References, Links and Notes 117 | - [EasyEDA Design Files](https://oshwlab.com/wagiminator) 118 | - [CNLohr: ch32003fun](https://github.com/cnlohr/ch32v003fun) 119 | - [WCH: CH32V003 datasheets](http://www.wch-ic.com/products/CH32V003.html) 120 | - [WCH: WCH-Link user manual](http://www.wch-ic.com/downloads/WCH-LinkUserManual_PDF.html) 121 | - [WCH: Official Store on AliExpress](https://wchofficialstore.aliexpress.com) 122 | - [KTMicro: KT0803K datasheet](https://www.elechouse.com/elechouse/images/product/FM%20Transmitter%20Module/KT0803K.pdf) 123 | - [KTMicro: KT0803L datasheet](http://radio-z.ucoz.lv/kt_0803/KT0803L_V1.3.pdf) 124 | - [Microne: ME6209 datasheet](https://datasheet.lcsc.com/lcsc/1811081822_MICRONE-Nanjing-Micro-One-Elec-ME6209A33M3G_C83508.pdf) 125 | - [TP4054 datasheet](https://media.digikey.com/pdf/Data%20Sheets/UTD%20Semi%20PDFs/TP4054.pdf) 126 | - [SSD1306 datasheet](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) 127 | - [128x32 OLED on Aliexpress](http://aliexpress.com/wholesale?SearchText=128+32+0.91+oled) 128 | - [CH32V003 FM Radio Receiver](https://github.com/wagiminator/CH32V003-FM-Receiver) 129 | 130 | ![FM_Transmitter_pic2.jpg](https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/main/documentation/FM_Transmitter_pic2.jpg) 131 | 132 | # License 133 | 134 | ![license.png](https://i.creativecommons.org/l/by-sa/3.0/88x31.png) 135 | 136 | This work is licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License. 137 | (http://creativecommons.org/licenses/by-sa/3.0/) 138 | -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic1.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic2.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic3.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic4.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic5.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic6.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_pic7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_pic7.jpg -------------------------------------------------------------------------------- /documentation/FM_Transmitter_wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/FM_Transmitter_wiring.png -------------------------------------------------------------------------------- /documentation/KT0803K_datasheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/documentation/KT0803K_datasheet.pdf -------------------------------------------------------------------------------- /hardware/FM_Transmitter_BOM.tsv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/hardware/FM_Transmitter_BOM.tsv -------------------------------------------------------------------------------- /hardware/FM_Transmitter_gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/hardware/FM_Transmitter_gerber.zip -------------------------------------------------------------------------------- /hardware/FM_Transmitter_schematic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/hardware/FM_Transmitter_schematic.pdf -------------------------------------------------------------------------------- /software/fm_transmitter/bin/fm_transmitter.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wagiminator/CH32V003-FM-Transmitter/47b0334d5df5c83413f5640c65df162e4ea6312a/software/fm_transmitter/bin/fm_transmitter.bin -------------------------------------------------------------------------------- /software/fm_transmitter/bin/fm_transmitter.hex: -------------------------------------------------------------------------------- 1 | :040000006F00E0634A 2 | :10000400B756004083D78641898BEDFF83D70640DE 3 | :10001400C207C18393E707102390F640B756004008 4 | :1000240083D74641858BEDDF420541812398A64065 5 | :10003400B756004083D74641898BEDDF83D786418D 6 | :100044008280B756004083D7464193F70708E5DF1F 7 | :10005400420541812398A6408280B756004083D749 8 | :100064004641918BEDDF83D70640C207C18393E7F6 9 | :1000740007202390F640828051111305C00706C45F 10 | :1000840022C2BD3F014537040020653F130404002C 11 | :10009400034504007D3703451400653703452400F8 12 | :1000A4004D371244A24031014DBFB707002093875A 13 | :1000B400070003C717002380A7002181619B1D89C6 14 | :1000C400598DA380A7004DBF8D4763E5A7023385F3 15 | :1000D400A74037070020130707008347170013754D 16 | :1000E400F50F0E0593F777FC137585035D8DA3005B 17 | :1000F400A70059B70505F1BFB727014003A78740FB 18 | :10010400B7064000558F23A4E740372701408327D3 19 | :100114000740898BEDDF8327C7440145C207C183AC 20 | :10012400938681801317150036970357070063E3FE 21 | :10013400E700828005051375F50FEDB7511122C252 22 | :1001440026C006C48D47AA84014463E3B7002E8405 23 | :100154002388918013058007A38881805535014544 24 | :10016400CD35130404FB1375F40FE13D13F5F400CE 25 | :10017400C93D13D5440013650501E1351244A2407D 26 | :1001840082443101D9BD03C71181211193871181A3 27 | :1001940022C826C606CAAA842E843AC03EC27D144A 28 | :1001A4001374F40F9307F00F631DF40003C501816A 29 | :1001B40042448245D2402695B2441375F50F61013D 30 | :1001C400B5BF130580072D3D130500049D3DA6878B 31 | :1001D40001453EC4BD35A247FD1793F7F70FEDFB6C 32 | :1001E400AD3D924783C5070003C50181850593F59D 33 | :1001F400F50FA9376DB7B7F700E09C4737F700E074 34 | :100204003E951C47898FE3CE07FE828003C7118188 35 | :1002140011119387118122CA26C806CC2AC0AE8444 36 | :100224003AC215443EC47D141374F40F01EC03C5A3 37 | :10023400018152449245E2402695C2441375F50F5C 38 | :100244007101EDBD13058007653B13050004D53B23 39 | :1002540082473387970003C5070085073AC63EC027 40 | :10026400CD3382473247E398E7FEC53BA24783C5B7 41 | :10027400070003C50181850593F5F50FC13565B701 42 | :10028400311122C626C406C82AC02EC28144114494 43 | :100294007D141374F40F9307F00F6317F400C24036 44 | :1002A4003244A244510182808147131714009307FA 45 | :1002B400406CBA9783D707000147824663FBF60474 46 | :1002C40092476315F400850493F4F40FB9CC131525 47 | :1002D400470093175700AA97C207C1830A07BA9722 48 | :1002E400C2070145C18313050075B5453E95393FE5 49 | :1002F40091450D45493D9247C1DF8547E31AF4F81E 50 | :1003040001458D4513054074113791450D45A53DB3 51 | :1003140041B7824605071377F70F3385F6409316E6 52 | :100324000501C18236C0854449BF91453545A13D8B 53 | :10033400C1B751118145014506C422C2013583C7A5 54 | :100344002181ADC303C46180894763F987020565D0 55 | :10035400130585A4B5455D3D8D476315F4028145BC 56 | :100364000145393F930500025545313D0145F545A9 57 | :100374001305006D1244A240310149BD0565130502 58 | :1003840045A1C9BF01C499476315F40081453145AE 59 | :10039400C9BF1374B40F854781452145E303F4FCB9 60 | :1003A4001145C1B703D541808545D93D9305000268 61 | :1003B4003545D13B05659305C002130545965DBFE0 62 | :1003C400B717014003A707807D769305F60F411107 63 | :1003D4006D8F22C426C206C61367071023A0E780C8 64 | :1003E40003A70780B705F1FFFD151377F7F013672F 65 | :1003F400070123A0E78098433D06372401406D8F11 66 | :1004040098C3914523A8B780094723A8E7803717E5 67 | :100414000240144F13050005814493E6160114CFDE 68 | :100424009443F18E0566130606DDD18E94C35C4FAA 69 | :10043400B7062000D58F5CCFB7560040E1772392F2 70 | :10044400B6409907239EF64085472390F6401C4FFB 71 | :1004540093E717201CCFB7070E0085072324F44029 72 | :10046400593B83278440B726014093E78700232420 73 | :10047400F44003A78640218B6DFF03A786401367D2 74 | :10048400470023A4E640372701400324874093070D 75 | :10049400074011887DF8370700407D1798CB372532 76 | :1004A40006000947D8DB130505A8B13313058007F7 77 | :1004B400813E014579368567938787953387870021 78 | :1004C400034507000504B53E85672D479387879547 79 | :1004D400E316E4FE593611447D141374F40F9307A4 80 | :1004E400F00F6317F406B13537153D001305059079 81 | :1004F400193303D5418013844180453E03C561808F 82 | :10050400E136938761803EC0C53E138721818347CE 83 | :100514000700C1C78947630DF5068D476302F506D9 84 | :100524008547631AF506824703C50700954763E9C3 85 | :10053400A700824705051375F50F2380A7006936C8 86 | :10054400CD3B5D3E7DFD5165130505886D316DBF65 87 | :10055400A2850145E536130580075D3413050004C3 88 | :10056400CD349307000801453EC0E13C8247FD17A6 89 | :1005740093F7F70FEDFBD534A2850145C136A9BF2A 90 | :10058400824703C507004DDD7D151375F50F75B75B 91 | :100594002300070075B72DD965B789468357040032 92 | :1005A4006304D5060D476309E5000547850763061F 93 | :1005B400E5009304200321A0FD172310F40037075E 94 | :1005C40000201307070083471700034707008356DB 95 | :1005D4000400A20793F70770D98FE387F6F2930715 96 | :1005E400A03663EED702930780432310F40003552B 97 | :1005F4000400653C3D3BFD1493F4F40F9307F00FA6 98 | :100604006396F402954409B785472300F700153330 99 | :10061400E5347DFD516513050588F13E4DB793071B 100 | :100624008043E3F6D7FC9307B036C1B7F13479D9E8 101 | :10063400516513050588C1367DBF9307403C97017A 102 | :1006440000209381E11B13810100130580087310BE 103 | :1006540005308D4573904580170500001305459AB4 104 | :100664004D8D7310553073901734B707002005670C 105 | :10067400938707001307C7A79386018163E7D7020F 106 | :10068400938701811387418163E7E702B71702402B 107 | :1006940013070002D8C3954637F700E014C3984FF8 108 | :1006A4001367470398CF7300203010431107910755 109 | :1006B40023AEC7FEE1B7910723AE07FEF1B70000F2 110 | :1006C40001000A006400E803102700000000000095 111 | :1006D400000000000000F0F8FC000000FCF8F0004E 112 | :1006E400000000000000000000000080C0C0C0C086 113 | :1006F400C0C0C09F3F7F0000007F3F9FC0C0C0C0FC 114 | :10070400C0C0C0800000FFFEFC0101010101010125 115 | :10071400FCFEFF000000FFFEFC01010101010101DC 116 | :10072400FCFEFF1F4F6770707070707070674F1F12 117 | :100734000000001F4F6770707070707070674F1FFB 118 | :10074400000000000000000000707070FCF9F30766 119 | :10075400070707070707F3F9FC7F3F1F00000000A6 120 | :100764000000001F3F7FFFFEFC00000000000000AF 121 | :10077400FCFEFF1F4F6770707070707070674F1FC2 122 | :1007840000000000000000000000F0F8FC00000081 123 | :10079400000000000000001F3F7F00000000000078 124 | :1007A40000000000FCFEFF0000000000000000004C 125 | :1007B40000070F1F00010307070707070707F3F9DF 126 | :1007C400FC000080C0C0C0C0C0C0C09F3F7FFFFE0F 127 | :1007D400FC010101010101010000001F4F6770705D 128 | :1007E400707070707060400000010307070707070E 129 | :1007F4000707F3F9FC000080C0C0C0C0C0C0C09FA0 130 | :100804003F7F00000001010101010101FCFEFF0026 131 | :10081400406070707070707070674F1FFCF8F0006B 132 | :10082400000000000000F0F8FC7F3F9FC0C0C0C083 133 | :10083400C0C0C09F3F7F0000000101010101010110 134 | :10084400FCFEFF00000000000000000000070F1F76 135 | :10085400FCF9F3070707070707070301007F3F9F1A 136 | :10086400C0C0C0C0C0C0C0800000000000010101C1 137 | :1008740001010101FCFEFF00406070707070707037 138 | :1008840070674F1FFCF9F307070707070707030102 139 | :10089400007F3F9FC0C0C0C0C0C0C0800000FFFE3A 140 | :1008A400FC01010101010101FCFEFF1F4F67707093 141 | :1008B4007070707070674F1FFCF9F3070707070724 142 | :1008C4000707F3F9FC7F3F1F000000000000001F32 143 | :1008D4003F7F00000000000000000000FCFEFF005D 144 | :1008E400000000000000000000070F1FFCF9F307E0 145 | :1008F400070707070707F3F9FC7F3F9FC0C0C0C085 146 | :10090400C0C0C09F3F7FFFFEFC0101010101010146 147 | :10091400FCFEFF1F4F6770707070707070674F1F20 148 | :10092400FCF9F307070707070707F3F9FC7F3F9F65 149 | :10093400C0C0C0C0C0C0C09F3F7F00000001010113 150 | :1009440001010101FCFEFF00406070707070707066 151 | :1009540070674F1FA81F8D142000DA02A1C8AF00D2 152 | :10096400FCF9F30707E7F7E70707F3F9FC000000D2 153 | :10097400FCF8F000000000000000F0F8FC000000AB 154 | :100984000000000000000000000000007F3F1F0086 155 | :10099400001F3F1F00001F3F7F0000007F3F9FC0DC 156 | :1009A400C0C0C0C0C0C09F3F7F0000000080C0C066 157 | :1009B400C0C0C0C0C0800000FFFEFC0000000000FA 158 | :1009C4000000FCFEFF000000FFFEFC01010101012C 159 | :1009D4000101FCFEFF00000000000181E1F97D1D22 160 | :1009E400010000001F0F0700000000000000070FB7 161 | :1009F4001F0000001F0F0700000000000000070F89 162 | :100A04001F00000040607677737170707060400062 163 | :100A14000000000000000000000000000000008052 164 | :100A2400C0C0C0C0C0C0C0800000000000010101FF 165 | :100A340001010101000000000000000000000000AE 166 | :100A440000000000000000000000000000000000A2 167 | :100A5400000080C0C0C0FCFEFCC0C0C0800000001C 168 | :100A64000101011F3F1F01010100000000000000FF 169 | :080A740000000000000000007A 170 | :100A7C0081C34100DC03030052034E02FA00000064 171 | :00000001FF 172 | -------------------------------------------------------------------------------- /software/fm_transmitter/ld/ch32v003.ld: -------------------------------------------------------------------------------- 1 | ENTRY( jump_reset ) 2 | 3 | MEMORY 4 | { 5 | FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16K 6 | RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 2K 7 | } 8 | 9 | SECTIONS 10 | { 11 | .init : 12 | { 13 | _sinit = .; 14 | . = ALIGN(4); 15 | KEEP(*(SORT_NONE(.init.jump))) 16 | KEEP(*(SORT_NONE(.init.vectors))) 17 | . = ALIGN(4); 18 | _einit = .; 19 | } >FLASH AT>FLASH 20 | 21 | .text : 22 | { 23 | . = ALIGN(4); 24 | *(.text) 25 | *(.text.*) 26 | *(.rodata) 27 | *(.rodata*) 28 | *(.gnu.linkonce.t.*) 29 | . = ALIGN(4); 30 | } >FLASH AT>FLASH 31 | 32 | .fini : 33 | { 34 | KEEP(*(SORT_NONE(.fini))) 35 | . = ALIGN(4); 36 | } >FLASH AT>FLASH 37 | 38 | PROVIDE(_etext = .); 39 | PROVIDE(_eitcm = .); 40 | 41 | .preinit_array : 42 | { 43 | PROVIDE_HIDDEN(__preinit_array_start = .); 44 | KEEP(*(.preinit_array)) 45 | PROVIDE_HIDDEN(__preinit_array_end = .); 46 | } >FLASH AT>FLASH 47 | 48 | .init_array : 49 | { 50 | PROVIDE_HIDDEN(__init_array_start = .); 51 | KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)SORT_BY_INIT_PRIORITY(.ctors.*))) 52 | KEEP(*(.init_array EXCLUDE_FILE(*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o) .ctors)) 53 | PROVIDE_HIDDEN(__init_array_end = .); 54 | } >FLASH AT>FLASH 55 | 56 | .fini_array : 57 | { 58 | PROVIDE_HIDDEN(__fini_array_start = .); 59 | KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) 60 | KEEP(*(.fini_array EXCLUDE_FILE(*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o) .dtors)) 61 | PROVIDE_HIDDEN(__fini_array_end = .); 62 | } >FLASH AT>FLASH 63 | 64 | .ctors : 65 | { 66 | KEEP(*crtbegin.o(.ctors)) 67 | KEEP(*crtbegin?.o(.ctors)) 68 | KEEP(*(EXCLUDE_FILE(*crtend.o *crtend?.o) .ctors)) 69 | KEEP(*(SORT(.ctors.*))) 70 | KEEP(*(.ctors)) 71 | } >FLASH AT>FLASH 72 | 73 | .dtors : 74 | { 75 | KEEP(*crtbegin.o(.dtors)) 76 | KEEP(*crtbegin?.o(.dtors)) 77 | KEEP(*(EXCLUDE_FILE(*crtend.o *crtend?.o) .dtors)) 78 | KEEP(*(SORT(.dtors.*))) 79 | KEEP(*(.dtors)) 80 | } >FLASH AT>FLASH 81 | 82 | .dalign : 83 | { 84 | . = ALIGN(4); 85 | PROVIDE(_data_vma = .); 86 | } >RAM AT>FLASH 87 | 88 | .dlalign : 89 | { 90 | . = ALIGN(4); 91 | PROVIDE(_data_lma = .); 92 | } >FLASH AT>FLASH 93 | 94 | .data : 95 | { 96 | . = ALIGN(4); 97 | *(.gnu.linkonce.r.*) 98 | *(.data .data.*) 99 | *(.gnu.linkonce.d.*) 100 | . = ALIGN(8); 101 | PROVIDE(__global_pointer$ = . + 0x800); 102 | *(.sdata .sdata.*) 103 | *(.sdata2*) 104 | *(.gnu.linkonce.s.*) 105 | . = ALIGN(8); 106 | *(.srodata.cst16) 107 | *(.srodata.cst8) 108 | *(.srodata.cst4) 109 | *(.srodata.cst2) 110 | *(.srodata .srodata.*) 111 | . = ALIGN(4); 112 | PROVIDE(_edata = .); 113 | } >RAM AT>FLASH 114 | 115 | .bss : 116 | { 117 | . = ALIGN(4); 118 | PROVIDE(_sbss = .); 119 | *(.sbss*) 120 | *(.gnu.linkonce.sb.*) 121 | *(.bss*) 122 | *(.gnu.linkonce.b.*) 123 | *(COMMON*) 124 | . = ALIGN(4); 125 | PROVIDE(_ebss = .); 126 | } >RAM AT>FLASH 127 | 128 | PROVIDE(_end = _ebss); 129 | PROVIDE(end = . ); 130 | PROVIDE(_eusrstack = ORIGIN(RAM) + LENGTH(RAM)); 131 | } 132 | -------------------------------------------------------------------------------- /software/fm_transmitter/makefile: -------------------------------------------------------------------------------- 1 | # =================================================================================== 2 | # Project Makefile 3 | # =================================================================================== 4 | # Project: CH32V003 + KT0803 K/L FM Transmitter 5 | # Author: Stefan Wagner 6 | # Year: 2023 7 | # URL: https://github.com/wagiminator 8 | # =================================================================================== 9 | # Install toolchain: 10 | # sudo apt install build-essential libnewlib-dev gcc-riscv64-unknown-elf 11 | # sudo apt install python3 python3-pip 12 | # pip install rvprog 13 | # 14 | # Provide access permission to WCH-LinkE programmer: 15 | # echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8010", MODE="666"' | sudo tee /etc/udev/rules.d/99-WCH-LinkE.rules 16 | # echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="8012", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-WCH-LinkE.rules 17 | # sudo udevadm control --reload-rules 18 | # 19 | # Connect WCH-LinkE programmer to your board. Type "make flash" in the command line. 20 | # =================================================================================== 21 | 22 | # Files and Folders 23 | TARGET = fm_transmitter 24 | INCLUDE = include 25 | SOURCE = src 26 | BIN = bin 27 | 28 | # Microcontroller Settings 29 | F_CPU = 8000000 30 | LDSCRIPT = ld/ch32v003.ld 31 | CPUARCH = -march=rv32ec -mabi=ilp32e 32 | 33 | # Toolchain 34 | PREFIX = riscv64-unknown-elf 35 | CC = $(PREFIX)-gcc 36 | OBJCOPY = $(PREFIX)-objcopy 37 | OBJDUMP = $(PREFIX)-objdump 38 | OBJSIZE = $(PREFIX)-size 39 | NEWLIB = /usr/include/newlib 40 | ISPTOOL = rvprog -f $(BIN)/$(TARGET).bin 41 | CLEAN = rm -f *.lst *.obj *.cof *.list *.map *.eep.hex *.o *.d 42 | 43 | # Compiler Flags 44 | CFLAGS = -g -Os -flto -ffunction-sections -fdata-sections -fno-builtin -nostdlib 45 | CFLAGS += $(CPUARCH) -DF_CPU=$(F_CPU) -I$(NEWLIB) -I$(INCLUDE) -I$(SOURCE) -I. -Wall 46 | LDFLAGS = -T$(LDSCRIPT) -lgcc -Wl,--gc-sections,--build-id=none 47 | CFILES = $(wildcard ./*.c) $(wildcard $(SOURCE)/*.c) $(wildcard $(SOURCE)/*.S) 48 | 49 | # Symbolic Targets 50 | help: 51 | @echo "Use the following commands:" 52 | @echo "make all compile and build $(TARGET).elf/.bin/.hex/.asm" 53 | @echo "make hex compile and build $(TARGET).hex" 54 | @echo "make asm compile and disassemble to $(TARGET).asm" 55 | @echo "make bin compile and build $(TARGET).bin" 56 | @echo "make flash compile and upload to MCU" 57 | @echo "make clean remove all build files" 58 | 59 | $(BIN)/$(TARGET).elf: $(CFILES) 60 | @echo "Building $(BIN)/$(TARGET).elf ..." 61 | @mkdir -p $(BIN) 62 | @$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) 63 | 64 | $(BIN)/$(TARGET).lst: $(BIN)/$(TARGET).elf 65 | @echo "Building $(BIN)/$(TARGET).lst ..." 66 | @$(OBJDUMP) -S $^ > $(BIN)/$(TARGET).lst 67 | 68 | $(BIN)/$(TARGET).map: $(BIN)/$(TARGET).elf 69 | @echo "Building $(BIN)/$(TARGET).map ..." 70 | @$(OBJDUMP) -t $^ > $(BIN)/$(TARGET).map 71 | 72 | $(BIN)/$(TARGET).bin: $(BIN)/$(TARGET).elf 73 | @echo "Building $(BIN)/$(TARGET).bin ..." 74 | @$(OBJCOPY) -O binary $< $(BIN)/$(TARGET).bin 75 | 76 | $(BIN)/$(TARGET).hex: $(BIN)/$(TARGET).elf 77 | @echo "Building $(BIN)/$(TARGET).hex ..." 78 | @$(OBJCOPY) -O ihex $< $(BIN)/$(TARGET).hex 79 | 80 | $(BIN)/$(TARGET).asm: $(BIN)/$(TARGET).elf 81 | @echo "Disassembling to $(BIN)/$(TARGET).asm ..." 82 | @$(OBJDUMP) -d $(BIN)/$(TARGET).elf > $(BIN)/$(TARGET).asm 83 | 84 | all: $(BIN)/$(TARGET).lst $(BIN)/$(TARGET).map $(BIN)/$(TARGET).bin $(BIN)/$(TARGET).hex $(BIN)/$(TARGET).asm size 85 | 86 | elf: $(BIN)/$(TARGET).elf removetemp size 87 | 88 | bin: $(BIN)/$(TARGET).bin removetemp size removeelf 89 | 90 | hex: $(BIN)/$(TARGET).hex removetemp size removeelf 91 | 92 | asm: $(BIN)/$(TARGET).asm removetemp size removeelf 93 | 94 | flash: $(BIN)/$(TARGET).bin size removeelf 95 | @echo "Uploading to MCU ..." 96 | @$(ISPTOOL) 97 | 98 | clean: 99 | @echo "Cleaning all up ..." 100 | @$(CLEAN) 101 | @rm -f $(BIN)/$(TARGET).elf $(BIN)/$(TARGET).lst $(BIN)/$(TARGET).map $(BIN)/$(TARGET).bin $(BIN)/$(TARGET).hex $(BIN)/$(TARGET).asm 102 | 103 | size: 104 | @echo "------------------" 105 | @echo "FLASH: $(shell $(OBJSIZE) -d $(BIN)/$(TARGET).elf | awk '/[0-9]/ {print $$1 + $$2}') bytes" 106 | @echo "SRAM: $(shell $(OBJSIZE) -d $(BIN)/$(TARGET).elf | awk '/[0-9]/ {print $$2 + $$3}') bytes" 107 | @echo "------------------" 108 | 109 | removetemp: 110 | @echo "Removing temporary files ..." 111 | @$(CLEAN) 112 | 113 | removeelf: 114 | @echo "Removing $(BIN)/$(TARGET).elf ..." 115 | @rm -f $(BIN)/$(TARGET).elf 116 | -------------------------------------------------------------------------------- /software/fm_transmitter/platformio.ini: -------------------------------------------------------------------------------- 1 | ; =================================================================================== 2 | ; PlatformIO Project Configuration File 3 | ; =================================================================================== 4 | ; Project: CH32V003 + KT0803 K/L FM Transmitter 5 | ; Author: Stefan Wagner 6 | ; Year: 2023 7 | ; URL: https://github.com/wagiminator 8 | ; =================================================================================== 9 | ; Install PlatformIO and CH32V: 10 | ; https://pio-ch32v.readthedocs.io/en/latest/ 11 | ; https://github.com/Community-PIO-CH32V/platform-ch32v 12 | ; =================================================================================== 13 | 14 | [env:CH32V003] 15 | platform = https://github.com/Community-PIO-CH32V/platform-ch32v.git 16 | board = genericCH32V003J4M6 17 | 18 | build_flags = -I. -D F_CPU=8000000 19 | board_build.ldscript = $PROJECT_DIR/ld/ch32v003.ld 20 | board_build.use_lto = yes 21 | 22 | upload_protocol = minichlink 23 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/gpio.h: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic GPIO Functions for CH32V003 * v1.6 * 3 | // =================================================================================== 4 | // 5 | // Pins must be defined as PA0, PA1, .., PC0, PC1, etc. - e.g.: 6 | // #define PIN_LED PC0 // LED on pin PC0 7 | // 8 | // PIN functions available: 9 | // ------------------------ 10 | // PIN_input(PIN) Set PIN as INPUT (floating, no pullup/pulldown) 11 | // PIN_input_PU(PIN) Set PIN as INPUT with internal PULLUP resistor 12 | // PIN_input_PD(PIN) Set PIN as INPUT with internal PULLDOWN resistor 13 | // PIN_input_AN(PIN) Set PIN as INPUT for analog peripherals (e.g. ADC) (*) 14 | // PIN_output(PIN) Set PIN as OUTPUT (push-pull) 15 | // PIN_output_OD(PIN) Set PIN as OUTPUT (open-drain) 16 | // PIN_alternate(PIN) Set PIN as alternate output (push-pull) 17 | // PIN_alternate_OD(PIN) Set PIN as alternate output (open-drain) 18 | // 19 | // PIN_low(PIN) Set PIN output value to LOW (*) 20 | // PIN_high(PIN) Set PIN output value to HIGH 21 | // PIN_toggle(PIN) TOGGLE PIN output value 22 | // PIN_read(PIN) Read PIN input value 23 | // PIN_write(PIN, val) Write PIN output value (0 = LOW / 1 = HIGH) 24 | // 25 | // PIN interrupt and event functions available: 26 | // -------------------------------------------- 27 | // PIN_EVT_set(PIN,TYPE) Setup PIN event TYPE: 28 | // PIN_EVT_OFF, PIN_EVT_RISING, PIN_EVT_FALLING, PIN_EVT_BOTH 29 | // PIN_INT_set(PIN,TYPE) Setup PIN interrupt TYPE: 30 | // PIN_INT_OFF, PIN_INT_RISING, PIN_INT_FALLING, PIN_INT_BOTH 31 | // PIN_INT_enable() Enable PIN interrupts 32 | // PIN_INT_disable() Disable PIN interrupts 33 | // PIN_INTFLAG_read(PIN) Read interrupt flag of PIN 34 | // PIN_INTFLAG_clear(PIN) Clear interrupt flag of PIN 35 | // PIN_INT_ISR { } Pin interrupt service routine 36 | // 37 | // PORT functions available: 38 | // ------------------------- 39 | // PORT_enable(PIN) Enable GPIO PORT of PIN 40 | // PORTA_enable() Enable GPIO PORT A 41 | // PORTC_enable() Enable GPIO PORT C 42 | // PORTD_enable() Enable GPIO PORT D 43 | // PORTS_enable() Enable all GPIO PORTS 44 | // 45 | // PORT_disable(PIN) Disable GPIO PORT of PIN 46 | // PORTA_disable() Disable GPIO PORT A 47 | // PORTC_disable() Disable GPIO PORT C 48 | // PORTD_disable() Disable GPIO PORT D 49 | // PORTS_disable() Disable all GPIO PORTS 50 | // 51 | // Analog-to-Digital Converter (ADC) functions available: 52 | // ------------------------------------------------------ 53 | // ADC_init() Init, enable and calibrate ADC (must be called first) 54 | // ADC_enable() Enable ADC (power-up) 55 | // ADC_disable() Disable ADC (power-down) 56 | // ADC_calibrate() Calibrate ADC 57 | // 58 | // ADC_fast() Set fast mode ( 28 clock cycles, least accurate) (*) 59 | // ADC_medium() Set medium mode (168 clock cycles, medium accurate) 60 | // ADC_slow() Set slow mode (504 clock cycles, most accurate) 61 | // 62 | // ADC_input(PIN) Set PIN as ADC input 63 | // ADC_input_VREF() Set internal voltage referece (Vref) as ADC input 64 | // ADC_input_VCAL() Set calibration voltage (Vcal) as ADC input 65 | // 66 | // ADC_read() Sample and read ADC value (0..1023) 67 | // ADC_read_VDD() Sample and read supply voltage (VDD) in millivolts (mV) 68 | // 69 | // Op-Amp Comparator (OPA) functions available: 70 | // -------------------------------------------- 71 | // OPA_enable() Enable OPA comparator 72 | // OPA_disable() Disable OPA comparator 73 | // OPA_negative(PIN) Set OPA inverting input PIN (PA1, PD0 only) 74 | // OPA_positive(PIN) Set OPA non-inverting input PIN (PA2, PD7 only) 75 | // OPA_output() Enable OPA output (push-pull) on pin PD4 76 | // OPA_output_OD() Enable OPA output (open-drain) on pin PD4 77 | // OPA_read() Read OPA output (0: pos < neg, 1: pos > neg) 78 | // 79 | // Notes: 80 | // ------ 81 | // - (*) default state 82 | // - For interrupts and events: Each PIN number can only be used once simultaneously. 83 | // (For example, PA1 and PC1 cannot be used simultaneously, but PA1 and PC2). 84 | // - Pins used for ADC must be set with PIN_input_AN beforehand. Only the following 85 | // pins can be used as INPUT for the ADC: PA1, PA2, PC4, PD2, PD3, PD4, PD5, PD6. 86 | // - Pins used as input for OPA comparator must be set with PIN_input_AN beforehand. 87 | // Only the following pins can be used for the OPA: PA1 or PD0 as negative 88 | // (inverting) input, PA2 or PD7 as positive (non-inverting) input and PD4 as 89 | // ouput. 90 | // 91 | // 2023 by Stefan Wagner: https://github.com/wagiminator 92 | 93 | #pragma once 94 | 95 | #ifdef __cplusplus 96 | extern "C" { 97 | #endif 98 | 99 | #include "system.h" 100 | 101 | // =================================================================================== 102 | // Enumerate PIN designators (use these designators to define pins) 103 | // =================================================================================== 104 | enum{ PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, 105 | PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7, 106 | PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7}; 107 | 108 | // =================================================================================== 109 | // Set PIN as INPUT (high impedance, no pullup/pulldown) 110 | // =================================================================================== 111 | #define PIN_input(PIN) \ 112 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR = (GPIOA->CFGLR \ 113 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 114 | | ((uint32_t)0b0100<<(((PIN)&7)<<2)) ) : \ 115 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR = (GPIOC->CFGLR \ 116 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 117 | | ((uint32_t)0b0100<<(((PIN)&7)<<2)) ) : \ 118 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR = (GPIOD->CFGLR \ 119 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 120 | | ((uint32_t)0b0100<<(((PIN)&7)<<2)) ) : \ 121 | (0)))) 122 | #define PIN_input_HI PIN_input 123 | #define PIN_input_FL PIN_input 124 | 125 | // =================================================================================== 126 | // Set PIN as INPUT with internal PULLUP resistor 127 | // =================================================================================== 128 | #define PIN_input_PU(PIN) \ 129 | ((PIN>=PA0)&&(PIN<=PA7) ? ({GPIOA->CFGLR = (GPIOA->CFGLR \ 130 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 131 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 132 | GPIOA->BSHR = ((uint32_t)1<<((PIN)&7)); }) : \ 133 | ((PIN>=PC0)&&(PIN<=PC7) ? ({GPIOC->CFGLR = (GPIOC->CFGLR \ 134 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 135 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 136 | GPIOC->BSHR = ((uint32_t)1<<((PIN)&7)); }) : \ 137 | ((PIN>=PD0)&&(PIN<=PD7) ? ({GPIOD->CFGLR = (GPIOD->CFGLR \ 138 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 139 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 140 | GPIOD->BSHR = ((uint32_t)1<<((PIN)&7)); }) : \ 141 | (0)))) 142 | 143 | // =================================================================================== 144 | // Set PIN as INPUT with internal PULLDOWN resistor 145 | // =================================================================================== 146 | #define PIN_input_PD(PIN) \ 147 | ((PIN>=PA0)&&(PIN<=PA7) ? ({GPIOA->CFGLR = (GPIOA->CFGLR \ 148 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 149 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 150 | GPIOA->BCR = ((uint32_t)1<<((PIN)&7)); }) : \ 151 | ((PIN>=PC0)&&(PIN<=PC7) ? ({GPIOC->CFGLR = (GPIOC->CFGLR \ 152 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 153 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 154 | GPIOC->BCR = ((uint32_t)1<<((PIN)&7)); }) : \ 155 | ((PIN>=PD0)&&(PIN<=PD7) ? ({GPIOD->CFGLR = (GPIOD->CFGLR \ 156 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 157 | | ((uint32_t)0b1000<<(((PIN)&7)<<2)); \ 158 | GPIOD->BCR = ((uint32_t)1<<((PIN)&7)); }) : \ 159 | (0)))) 160 | 161 | // =================================================================================== 162 | // Set PIN as INPUT for analog peripherals (e.g. ADC) 163 | // =================================================================================== 164 | #define PIN_input_AN(PIN) \ 165 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR &= ~((uint32_t)0b1111<<(((PIN)&7)<<2)) ) : \ 166 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR &= ~((uint32_t)0b1111<<(((PIN)&7)<<2)) ) : \ 167 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR &= ~((uint32_t)0b1111<<(((PIN)&7)<<2)) ) : \ 168 | (0)))) 169 | #define PIN_input_AD PIN_input_AN 170 | #define PIN_input_ADC PIN_input_AN 171 | 172 | // =================================================================================== 173 | // Set PIN as OUTPUT (push-pull, maximum speed 10MHz) 174 | // =================================================================================== 175 | #define PIN_output(PIN) \ 176 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR = (GPIOA->CFGLR \ 177 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 178 | | ((uint32_t)0b0001<<(((PIN)&7)<<2)) ) : \ 179 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR = (GPIOC->CFGLR \ 180 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 181 | | ((uint32_t)0b0001<<(((PIN)&7)<<2)) ) : \ 182 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR = (GPIOD->CFGLR \ 183 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 184 | | ((uint32_t)0b0001<<(((PIN)&7)<<2)) ) : \ 185 | (0)))) 186 | #define PIN_output_PP PIN_output 187 | 188 | // =================================================================================== 189 | // Set PIN as OUTPUT OPEN-DRAIN (maximum speed 10MHz) 190 | // =================================================================================== 191 | #define PIN_output_OD(PIN) \ 192 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR = (GPIOA->CFGLR \ 193 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 194 | | ((uint32_t)0b0101<<(((PIN)&7)<<2)) ) : \ 195 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR = (GPIOC->CFGLR \ 196 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 197 | | ((uint32_t)0b0101<<(((PIN)&7)<<2)) ) : \ 198 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR = (GPIOD->CFGLR \ 199 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 200 | | ((uint32_t)0b0101<<(((PIN)&7)<<2)) ) : \ 201 | (0)))) 202 | 203 | // =================================================================================== 204 | // Set PIN as alternate output (push-pull, maximum speed 10MHz) 205 | // =================================================================================== 206 | #define PIN_alternate(PIN) \ 207 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR = (GPIOA->CFGLR \ 208 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 209 | | ((uint32_t)0b1001<<(((PIN)&7)<<2)) ) : \ 210 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR = (GPIOC->CFGLR \ 211 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 212 | | ((uint32_t)0b1001<<(((PIN)&7)<<2)) ) : \ 213 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR = (GPIOD->CFGLR \ 214 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 215 | | ((uint32_t)0b1001<<(((PIN)&7)<<2)) ) : \ 216 | (0)))) 217 | #define PIN_alternate_PP PIN_alternate 218 | 219 | // =================================================================================== 220 | // Set PIN as alternate output (open-drain, maximum speed 10MHz) 221 | // =================================================================================== 222 | #define PIN_alternate_OD(PIN) \ 223 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->CFGLR = (GPIOA->CFGLR \ 224 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 225 | | ((uint32_t)0b1101<<(((PIN)&7)<<2)) ) : \ 226 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->CFGLR = (GPIOC->CFGLR \ 227 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 228 | | ((uint32_t)0b1101<<(((PIN)&7)<<2)) ) : \ 229 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->CFGLR = (GPIOD->CFGLR \ 230 | & ~((uint32_t)0b1111<<(((PIN)&7)<<2))) \ 231 | | ((uint32_t)0b1101<<(((PIN)&7)<<2)) ) : \ 232 | (0)))) 233 | 234 | // =================================================================================== 235 | // Set PIN output value to LOW 236 | // =================================================================================== 237 | #define PIN_low(PIN) \ 238 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->BCR = 1<<((PIN)&7) ) : \ 239 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->BCR = 1<<((PIN)&7) ) : \ 240 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->BCR = 1<<((PIN)&7) ) : \ 241 | (0)))) 242 | 243 | // =================================================================================== 244 | // Set PIN output value to HIGH 245 | // =================================================================================== 246 | #define PIN_high(PIN) \ 247 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->BSHR = 1<<((PIN)&7) ) : \ 248 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->BSHR = 1<<((PIN)&7) ) : \ 249 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->BSHR = 1<<((PIN)&7) ) : \ 250 | (0)))) 251 | 252 | // =================================================================================== 253 | // Toggle PIN output value 254 | // =================================================================================== 255 | #define PIN_toggle(PIN) \ 256 | ((PIN>=PA0)&&(PIN<=PA7) ? ( GPIOA->OUTDR ^= 1<<((PIN)&7) ) : \ 257 | ((PIN>=PC0)&&(PIN<=PC7) ? ( GPIOC->OUTDR ^= 1<<((PIN)&7) ) : \ 258 | ((PIN>=PD0)&&(PIN<=PD7) ? ( GPIOD->OUTDR ^= 1<<((PIN)&7) ) : \ 259 | (0)))) 260 | 261 | // =================================================================================== 262 | // Read PIN input value (returns 0 for LOW, 1 for HIGH) 263 | // =================================================================================== 264 | #define PIN_read(PIN) \ 265 | ((PIN>=PA0)&&(PIN<=PA7) ? ( (GPIOA->INDR>>((PIN)&7))&1 ) : \ 266 | ((PIN>=PC0)&&(PIN<=PC7) ? ( (GPIOC->INDR>>((PIN)&7))&1 ) : \ 267 | ((PIN>=PD0)&&(PIN<=PD7) ? ( (GPIOD->INDR>>((PIN)&7))&1 ) : \ 268 | (0)))) 269 | 270 | // =================================================================================== 271 | // Write PIN output value (0 = LOW / 1 = HIGH) 272 | // =================================================================================== 273 | #define PIN_write(PIN, val) (val)?(PIN_high(PIN)):(PIN_low(PIN)) 274 | 275 | // =================================================================================== 276 | // Setup PIN interrupt 277 | // =================================================================================== 278 | enum{PIN_INT_OFF, PIN_INT_RISING, PIN_INT_FALLING, PIN_INT_BOTH}; 279 | 280 | #define PIN_INT_set(PIN, TYPE) { \ 281 | ((PIN>=PA0)&&(PIN<=PA7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPAEN; \ 282 | AFIO->EXTICR &= ~((uint32_t)3<<(((PIN)&7)<<1)); }) : \ 283 | ((PIN>=PC0)&&(PIN<=PC7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPCEN; \ 284 | AFIO->EXTICR = (AFIO->EXTICR \ 285 | & ~((uint32_t)3<<(((PIN)&7)<<1))) \ 286 | | ((uint32_t)2<<(((PIN)&7)<<1)); }) : \ 287 | ((PIN>=PD0)&&(PIN<=PD7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPDEN; \ 288 | AFIO->EXTICR |= ((uint32_t)3<<(((PIN)&7)<<1)); }) : \ 289 | (0)))); \ 290 | (TYPE & 3) ? (EXTI->INTENR |= (uint32_t)1<<((PIN)&7)) : \ 291 | (EXTI->INTENR &= ~((uint32_t)1<<((PIN)&7))); \ 292 | (TYPE & 1) ? (EXTI->RTENR |= (uint32_t)1<<((PIN)&7)) : \ 293 | (EXTI->RTENR &= ~((uint32_t)1<<((PIN)&7))); \ 294 | (TYPE & 2) ? (EXTI->FTENR |= (uint32_t)1<<((PIN)&7)) : \ 295 | (EXTI->FTENR &= ~((uint32_t)1<<((PIN)&7))); \ 296 | } 297 | 298 | #define PIN_INT_enable() NVIC_EnableIRQ(EXTI7_0_IRQn) 299 | #define PIN_INT_disable() NVIC_DisableIRQ(EXTI7_0_IRQn) 300 | 301 | #define PIN_INTFLAG_read(PIN) (EXTI->INTFR & ((uint32_t)1 << ((PIN) & 7))) 302 | #define PIN_INTFLAG_clear(PIN) EXTI->INTFR = ((uint32_t)1 << ((PIN) & 7)) 303 | 304 | #define PIN_INT_ISR void EXTI7_0_IRQHandler(void) __attribute__((interrupt));\ 305 | void EXTI7_0_IRQHandler(void) 306 | 307 | // =================================================================================== 308 | // Setup PIN event 309 | // =================================================================================== 310 | enum{PIN_EVT_OFF, PIN_EVT_RISING, PIN_EVT_FALLING, PIN_EVT_BOTH}; 311 | 312 | #define PIN_EVT_set(PIN, TYPE) { \ 313 | ((PIN>=PA0)&&(PIN<=PA7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPAEN; \ 314 | AFIO->EXTICR &= ~((uint32_t)3<<(((PIN)&7)<<1)); }) : \ 315 | ((PIN>=PC0)&&(PIN<=PC7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPCEN; \ 316 | AFIO->EXTICR = (AFIO->EXTICR \ 317 | & ~((uint32_t)3<<(((PIN)&7)<<1))) \ 318 | | ((uint32_t)2<<(((PIN)&7)<<1)); }) : \ 319 | ((PIN>=PD0)&&(PIN<=PD7) ? ({RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPDEN; \ 320 | AFIO->EXTICR |= ((uint32_t)3<<(((PIN)&7)<<1)); }) : \ 321 | (0)))); \ 322 | (TYPE & 3) ? (EXTI->EVENR |= (uint32_t)1<<((PIN)&7)) : \ 323 | (EXTI->EVENR &= ~((uint32_t)1<<((PIN)&7))); \ 324 | (TYPE & 1) ? (EXTI->RTENR |= (uint32_t)1<<((PIN)&7)) : \ 325 | (EXTI->RTENR &= ~((uint32_t)1<<((PIN)&7))); \ 326 | (TYPE & 2) ? (EXTI->FTENR |= (uint32_t)1<<((PIN)&7)) : \ 327 | (EXTI->FTENR &= ~((uint32_t)1<<((PIN)&7))); \ 328 | } 329 | 330 | // =================================================================================== 331 | // Enable GPIO PORTS 332 | // =================================================================================== 333 | #define PORTA_enable() RCC->APB2PCENR |= RCC_IOPAEN; 334 | #define PORTC_enable() RCC->APB2PCENR |= RCC_IOPCEN; 335 | #define PORTD_enable() RCC->APB2PCENR |= RCC_IOPDEN; 336 | #define PORTS_enable() RCC->APB2PCENR |= RCC_IOPAEN | RCC_IOPCEN | RCC_IOPDEN 337 | 338 | #define PORT_enable(PIN) \ 339 | ((PIN>=PA0)&&(PIN<=PA7) ? ( RCC->APB2PCENR |= RCC_IOPAEN ) : \ 340 | ((PIN>=PC0)&&(PIN<=PC7) ? ( RCC->APB2PCENR |= RCC_IOPCEN ) : \ 341 | ((PIN>=PD0)&&(PIN<=PD7) ? ( RCC->APB2PCENR |= RCC_IOPDEN ) : \ 342 | (0)))) 343 | 344 | // =================================================================================== 345 | // Disable GPIO PORTS 346 | // =================================================================================== 347 | #define PORTA_disable() RCC->APB2PCENR &= ~RCC_IOPAEN 348 | #define PORTC_disable() RCC->APB2PCENR &= ~RCC_IOPCEN 349 | #define PORTD_disable() RCC->APB2PCENR &= ~RCC_IOPDEN 350 | #define PORTS_disable() RCC->APB2PCENR &= ~(RCC_IOPAEN | RCC_IOPCEN | RCC_IOPDEN) 351 | 352 | #define PORT_disable(PIN) \ 353 | ((PIN>=PA0)&&(PIN<=PA7) ? ( RCC->APB2PCENR &= ~RCC_IOPAEN ) : \ 354 | ((PIN>=PC0)&&(PIN<=PC7) ? ( RCC->APB2PCENR &= ~RCC_IOPCEN ) : \ 355 | ((PIN>=PD0)&&(PIN<=PD7) ? ( RCC->APB2PCENR &= ~RCC_IOPDEN ) : \ 356 | (0)))) 357 | 358 | // =================================================================================== 359 | // ADC Functions 360 | // =================================================================================== 361 | #define ADC_enable() ADC1->CTLR2 |= ADC_ADON 362 | #define ADC_disable() ADC1->CTLR2 &= ~ADC_ADON 363 | #define ADC_fast() ADC1->SAMPTR2 = 0b00000000000000000000000000000000 364 | #define ADC_slow() ADC1->SAMPTR2 = 0b00111111111111111111111111111111 365 | #define ADC_medium() ADC1->SAMPTR2 = 0b00110110110110110110110110110110 366 | 367 | #define ADC_input_VREF() ADC1->RSQR3 = 8 368 | #define ADC_input_VCAL() ADC1->RSQR3 = 9 369 | 370 | #define ADC_input(PIN) \ 371 | (PIN == PA1 ? (ADC1->RSQR3 = 1) : \ 372 | (PIN == PA2 ? (ADC1->RSQR3 = 0) : \ 373 | (PIN == PC4 ? (ADC1->RSQR3 = 2) : \ 374 | (PIN == PD2 ? (ADC1->RSQR3 = 3) : \ 375 | (PIN == PD3 ? (ADC1->RSQR3 = 4) : \ 376 | (PIN == PD4 ? (ADC1->RSQR3 = 7) : \ 377 | (PIN == PD5 ? (ADC1->RSQR3 = 5) : \ 378 | (PIN == PD6 ? (ADC1->RSQR3 = 6) : \ 379 | (0))))))))) 380 | 381 | static inline void ADC_calibrate(void) { 382 | ADC1->CTLR2 |= ADC_RSTCAL; // reset calibration 383 | while(ADC1->CTLR2 & ADC_RSTCAL); // wait until finished 384 | ADC1->CTLR2 |= ADC_CAL; // start calibration 385 | while(ADC1->CTLR2 & ADC_CAL); // wait until finished 386 | } 387 | 388 | static inline void ADC_init(void) { 389 | RCC->APB2PCENR |= RCC_ADC1EN | RCC_AFIOEN; // enable ADC and AFIO 390 | ADC1->CTLR2 = ADC_ADON | ADC_EXTSEL; // turn on ADC, software triggering 391 | DLY_us(10); // wait to settle 392 | ADC_calibrate(); // calibrate ADC 393 | } 394 | 395 | static inline uint16_t ADC_read(void) { 396 | ADC1->CTLR2 |= ADC_SWSTART; // start conversion 397 | while(!(ADC1->STATR & ADC_EOC)); // wait until finished 398 | return ADC1->RDATAR; // return result 399 | } 400 | 401 | static inline uint16_t ADC_read_VDD(void) { 402 | ADC_input_VREF(); // set VREF as ADC input 403 | return((uint32_t)1200 * 1023 / ADC_read()); // return VDD im mV 404 | } 405 | 406 | // =================================================================================== 407 | // OPA Functions 408 | // =================================================================================== 409 | #define OPA_enable() EXTEN->EXTEN_CTR |= EXTEN_OPA_EN 410 | #define OPA_disable() EXTEN->EXTEN_CTR &= ~EXTEN_OPA_EN 411 | #define OPA_read() ((GPIOD->INDR >> 4) & 1) 412 | 413 | #define OPA_negative(PIN) \ 414 | (PIN == PA1 ? (EXTEN->EXTEN_CTR &= ~EXTEN_OPA_NSEL) : \ 415 | (PIN == PD0 ? (EXTEN->EXTEN_CTR |= EXTEN_OPA_NSEL) : \ 416 | (0))) 417 | 418 | #define OPA_positive(PIN) \ 419 | (PIN == PA2 ? (EXTEN->EXTEN_CTR &= ~EXTEN_OPA_PSEL) : \ 420 | (PIN == PD7 ? (EXTEN->EXTEN_CTR |= EXTEN_OPA_PSEL) : \ 421 | (0))) 422 | 423 | #define OPA_output() { \ 424 | RCC->APB2PCENR |= RCC_AFIOEN; \ 425 | GPIOD->CFGLR = (GPIOD->CFGLR & ~((uint32_t)0b1111<<(4<<2))) \ 426 | | ((uint32_t)0b1001<<(4<<2)); \ 427 | } 428 | 429 | #define OPA_output_OD() { \ 430 | RCC->APB2PCENR |= RCC_AFIOEN; \ 431 | GPIOD->CFGLR = (GPIOD->CFGLR & ~((uint32_t)0b1111<<(4<<2))) \ 432 | | ((uint32_t)0b1101<<(4<<2)); \ 433 | } 434 | 435 | #define OPA_output_PP OPA_output 436 | 437 | // =================================================================================== 438 | // CMP Functions (alias) 439 | // =================================================================================== 440 | #define CMP_enable OPA_enable 441 | #define CMP_disable OPA_disable 442 | #define CMP_read OPA_read 443 | #define CMP_negative OPA_negative 444 | #define CMP_positive OPA_positive 445 | #define CMP_output OPA_output 446 | #define CMP_output_PP OPA_output_PP 447 | #define CMP_output_OD OPA_output_OD 448 | 449 | #ifdef __cplusplus 450 | }; 451 | #endif 452 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/i2c_tx.c: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic I2C Master Functions (write only) for CH32V003 * v1.3 * 3 | // =================================================================================== 4 | // 2023 by Stefan Wagner: https://github.com/wagiminator 5 | 6 | #include "i2c_tx.h" 7 | 8 | // Init I2C 9 | void I2C_init(void) { 10 | // Setup GPIO pins 11 | #if I2C_MAP == 0 12 | // Set pin PC1 (SDA) and PC2 (SCL) to output, open-drain, 10MHz, multiplex 13 | RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPCEN; 14 | GPIOC->CFGLR = (GPIOC->CFGLR & ~(((uint32_t)0b1111<<(1<<2)) | ((uint32_t)0b1111<<(2<<2)))) 15 | | (((uint32_t)0b1101<<(1<<2)) | ((uint32_t)0b1101<<(2<<2))); 16 | #elif I2C_MAP == 1 17 | // Set pin PD0 (SDA) and PD1 (SCL) to output, open-drain, 10MHz, multiplex 18 | RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPDEN; 19 | AFIO->PCFR1 |= 1<<1; 20 | GPIOD->CFGLR = (GPIOD->CFGLR & ~(((uint32_t)0b1111<<(0<<2)) | ((uint32_t)0b1111<<(1<<2)))) 21 | | (((uint32_t)0b1101<<(0<<2)) | ((uint32_t)0b1101<<(1<<2))); 22 | #elif I2C_MAP == 2 23 | // Set pin PC6 (SDA) and PC5 (SCL) to output, open-drain, 10MHz, multiplex 24 | RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPCEN; 25 | AFIO->PCFR1 |= 1<<22; 26 | GPIOC->CFGLR = (GPIOC->CFGLR & ~(((uint32_t)0b1111<<(6<<2)) | ((uint32_t)0b1111<<(5<<2)))) 27 | | (((uint32_t)0b1101<<(6<<2)) | ((uint32_t)0b1101<<(5<<2))); 28 | #else 29 | #warning Wrong I2C REMAP 30 | #endif 31 | 32 | // Setup and enable I2C 33 | RCC->APB1PCENR |= RCC_I2C1EN; // enable I2C module clock 34 | I2C1->CTLR2 = 4; // set input clock rate 35 | #if I2C_CLKRATE > 100000 // Fast mode ? 36 | I2C1->CKCFGR = (F_CPU / (3 * I2C_CLKRATE)) // -> set clock division factor 1:2 37 | | I2C_CKCFGR_FS; // -> enable fast mode (400kHz) 38 | #else // Standard mode? 39 | I2C1->CKCFGR = (F_CPU / (2 * I2C_CLKRATE)); // -> set clock division factor 1:1 40 | #endif 41 | I2C1->CTLR1 = I2C_CTLR1_PE; // enable I2C 42 | } 43 | 44 | // Start I2C transmission (addr must contain R/W bit) 45 | #pragma GCC diagnostic push 46 | #pragma GCC diagnostic ignored "-Wunused-variable" 47 | void I2C_start(uint8_t addr) { 48 | while(I2C1->STAR2 & I2C_STAR2_BUSY); // wait until bus ready 49 | I2C1->CTLR1 |= I2C_CTLR1_START; // set START condition 50 | while(!(I2C1->STAR1 & I2C_STAR1_SB)); // wait for START generated 51 | I2C1->DATAR = addr; // send slave address + R/W bit 52 | while(!(I2C1->STAR1 & I2C_STAR1_ADDR)); // wait for address transmitted 53 | uint16_t reg = I2C1->STAR2; // clear flags 54 | } 55 | #pragma GCC diagnostic pop 56 | 57 | // Send data byte via I2C bus 58 | void I2C_write(uint8_t data) { 59 | while(!(I2C1->STAR1 & I2C_STAR1_TXE)); // wait for last byte transmitted 60 | I2C1->DATAR = data; // send data byte 61 | } 62 | 63 | // Stop I2C transmission 64 | void I2C_stop(void) { 65 | while(!(I2C1->STAR1 & I2C_STAR1_BTF)); // wait for last byte transmitted 66 | I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition 67 | } 68 | 69 | // Send data buffer via I2C bus and stop 70 | void I2C_writeBuffer(uint8_t* buf, uint16_t len) { 71 | while(len--) I2C_write(*buf++); // write buffer 72 | I2C_stop(); // stop transmission 73 | } 74 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/i2c_tx.h: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic I2C Master Functions (write only) for CH32V003 * v1.3 * 3 | // =================================================================================== 4 | // 5 | // Functions available: 6 | // -------------------- 7 | // I2C_init() Init I2C with defined clock rate (400kHz) 8 | // I2C_start(addr) I2C start transmission, addr must contain R/W bit 9 | // I2C_write(b) I2C transmit one data byte via I2C 10 | // I2C_stop() I2C stop transmission 11 | // I2C_writeBuffer(buf,len) Send buffer (*buf) with length (len) via I2C and stop 12 | // 13 | // I2C pin mapping (set below in I2C parameters): 14 | // ---------------------------------------------- 15 | // I2C_MAP 0 1 2 16 | // SDA-pin PC1 PD0 PC6 17 | // SCL-pin PC2 PD1 PC5 18 | // 19 | // External pull-up resistors (4k7 - 10k) are mandatory! 20 | // 2023 by Stefan Wagner: https://github.com/wagiminator 21 | 22 | #pragma once 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #include "system.h" 29 | 30 | // I2C Parameters 31 | #define I2C_CLKRATE 400000 // I2C bus clock rate (Hz) 32 | #define I2C_MAP 0 // I2C pin mapping (see above) 33 | 34 | // I2C Functions 35 | void I2C_init(void); // I2C init function 36 | void I2C_start(uint8_t addr); // I2C start transmission, addr must contain R/W bit 37 | void I2C_write(uint8_t data); // I2C transmit one data byte via I2C 38 | void I2C_stop(void); // I2C stop transmission 39 | void I2C_writeBuffer(uint8_t* buf, uint16_t len); 40 | 41 | #ifdef __cplusplus 42 | }; 43 | #endif 44 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/kt0803.c: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic KT0803 K/L FM Transmitter Functions * v1.0 * 3 | // =================================================================================== 4 | // 2023 by Stefan Wagner: https://github.com/wagiminator 5 | 6 | #include "kt0803.h" 7 | 8 | // KT0803 registers 0x00 - 0x02 9 | uint8_t KT_regs[] = { 0x81, 0xC3, 0x41 }; 10 | 11 | // Update KT0803 registers 0x00 - 0x02 12 | void KT_update(void) { 13 | uint8_t i; 14 | I2C_start((KT_I2C_ADDR << 1) | 0); 15 | I2C_write(0); 16 | for(i=0; i<3; i++) I2C_write(KT_regs[i]); 17 | I2C_stop(); 18 | } 19 | 20 | // Setup KT0803 21 | void KT_init(void) { 22 | #if KT_INIT_I2C > 0 23 | I2C_init(); 24 | #endif 25 | KT_update(); 26 | } 27 | 28 | // Set frequency (in 100kHz steps, 885 means 88.5Mhz) 29 | void KT_setFreq(uint16_t freq) { 30 | KT_regs[0] = (uint8_t)freq; 31 | KT_regs[1] = (KT_regs[1] & 0xf8) | ((freq >> 8) & 0x07); 32 | KT_update(); 33 | } 34 | 35 | // Get current frequency (in 100kHz steps, 885 means 88.5Mhz) 36 | uint16_t KT_getFreq(void) { 37 | return( (((uint16_t)KT_regs[1] & 0x0007) << 8) | KT_regs[0] ); 38 | } 39 | 40 | // Set gain (0: -12dB, ... , 3: 0dB, ... , 6: +12dB) 41 | void KT_setGain(uint8_t gain) { 42 | (gain <= 3) ? (gain = 3 - gain) : (gain++); 43 | KT_regs[1] = (KT_regs[1] & 0xc7) | ((gain & 0x07) << 3); 44 | KT_update(); 45 | } 46 | 47 | // Set mude (0: unmute, 1: mute) 48 | void KT_setMute(uint8_t mute) { 49 | (mute) ? (KT_regs[2] |= 0x08) : (KT_regs[2] &= 0xf7); 50 | KT_update(); 51 | } 52 | 53 | // Set pre-emphasis time-constant depending on region 54 | void KT_setRegion(uint8_t region) { 55 | (region) ? (KT_regs[2] |= 0x01) : (KT_regs[2] &= 0xfe); 56 | KT_update(); 57 | } 58 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/kt0803.h: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic KT0803 K/L FM Transmitter Functions * v1.0 * 3 | // =================================================================================== 4 | // 5 | // Collection of the most necessary functions for controlling an KT0803 FM transmitter 6 | // IC. 7 | // 8 | // Functions available: 9 | // -------------------- 10 | // KT_init() KT0802 init function (write default values) 11 | // KT_getFreq() Get current frequency (in 100kHz steps, 885 means 88.5Mhz) 12 | // KT_setFreq(f) Set frequency (in 100kHz steps, 885 means 88.5Mhz) 13 | // KT_setGain(g) Set gain (0: -12dB, ... , 3: 0dB, ... , 6: +12dB) 14 | // KT_setMute(m) Set mude (0: unmute, 1: mute) 15 | // KT_setRegion(r) Set region (0: USA/Japan, 1: Europe/Australia) 16 | // 17 | // 2023 by Stefan Wagner: https://github.com/wagiminator 18 | 19 | #pragma once 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | #include "i2c_tx.h" // choose your I2C library 26 | 27 | // KT0802 parameters 28 | #define KT_INIT_I2C 0 // 1: init I2C with KT_init() 29 | 30 | // KT0802 I2C device address 31 | #define KT_I2C_ADDR 0x3e 32 | 33 | // Frequency range (in 100kHz) 34 | #define KT_FREQ_MIN 875 35 | #define KT_FREQ_MAX 1080 36 | 37 | // Pre-emphasis time-constant depending on region 38 | #define KT_USA_JAPAN 0 39 | #define KT_EUROPE_AUSTRALIA 1 40 | 41 | // KT0802 Functions 42 | void KT_init(void); // KT0802 init function (write default values) 43 | uint16_t KT_getFreq(void); // Get current frequency (in 100kHz steps, 885 means 88.5Mhz) 44 | void KT_setFreq(uint16_t freq); // Set frequency (in 100kHz steps, 885 means 88.5Mhz) 45 | void KT_setGain(uint8_t gain); // Set gain (0: -12dB, ... , 3: 0dB, ... , 6: +12dB) 46 | void KT_setMute(uint8_t mute); // Set mude (0: unmute, 1: mute) 47 | void KT_setRegion(uint8_t region); // Set pre-emphasis time-constant depending on region 48 | 49 | #ifdef __cplusplus 50 | }; 51 | #endif 52 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/main.c: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Project: CH32V003 + KT0803 K/L FM Transmitter 3 | // Version: v1.1 4 | // Year: 2023 5 | // Author: Stefan Wagner 6 | // Github: https://github.com/wagiminator 7 | // EasyEDA: https://easyeda.com/wagiminator 8 | // License: http://creativecommons.org/licenses/by-sa/3.0/ 9 | // =================================================================================== 10 | // 11 | // Description: 12 | // ------------ 13 | // Pocket Stereo FM Transmitter. 14 | // 15 | // References: 16 | // ----------- 17 | // - CNLohr ch32v003fun: https://github.com/cnlohr/ch32v003fun 18 | // - WCH Nanjing Qinheng Microelectronics: http://wch.cn 19 | // 20 | // Compilation Instructions: 21 | // ------------------------- 22 | // - Make sure GCC toolchain (gcc-riscv64-unknown-elf, newlib) and Python3 with rvprog 23 | // are installed. In addition, Linux requires access rights to WCH-LinkE programmer. 24 | // - Connect the WCH-LinkE programmer to the PROG-header of the device. 25 | // - Run 'make flash'. 26 | 27 | 28 | // =================================================================================== 29 | // Libraries, Definitions and Macros 30 | // =================================================================================== 31 | #include // system functions 32 | #include // GPIO functions 33 | #include // KT0803 functions 34 | #include // OLED functions 35 | 36 | #define PIN_SW PA2 // KT0803 switch on/off 37 | #define PIN_RST PA1 // KT0803 reset (active low) 38 | #define PIN_KEYS PC4 // Control keys 39 | 40 | uint8_t display = 0; // current display/control mode (0: frequency, 1: gain) 41 | uint8_t gain = 3; // current gain (0..6) 42 | uint16_t freq = 988; // current frequency (in 100kHz steps, 988 means 98.8Mhz) 43 | 44 | // =================================================================================== 45 | // OLED Bitmaps 46 | // =================================================================================== 47 | 48 | // "MHz" 44x32 pixel segment font 49 | const uint8_t OLED_MHZ[] = { 50 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0xE7, 0xF7, 0xE7, 0x07, 0x07, 0xF3, 0xF9, 0xFC, 0x00, 0x00, 0x00, 51 | 0xFC, 0xF8, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0xFC, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x7F, 0x3F, 0x1F, 0x00, 0x00, 0x1F, 0x3F, 0x1F, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x00, 0x00, 0x00, 54 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 0x00, 0x00, 0x00, 55 | 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 56 | 0xFF, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 0x00, 0x00, 0x00, 57 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x01, 0x81, 0xE1, 0xF9, 0x7D, 0x1D, 0x01, 0x00, 0x00, 0x00, 59 | 0x1F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1F, 0x00, 0x00, 0x00, 60 | 0x1F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1F, 0x00, 0x00, 0x00, 61 | 0x40, 0x60, 0x76, 0x77, 0x73, 0x71, 0x70, 0x70, 0x70, 0x60, 0x40, 0x00 62 | }; 63 | 64 | // "db" 29x32 pixel segment font 65 | const uint8_t OLED_DB[] = { 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0xFC, 0x00, 0x00, 0x00, 67 | 0xFC, 0xF8, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 0x00, 0x00, 0x00, 69 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 70 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 0x00, 0x00, 0x00, 71 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 72 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 0x00, 0x00, 0x00, 73 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F 74 | }; 75 | 76 | // "-" 13x32 pixel segment font 77 | const uint8_t OLED_MINUS[] = { 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 82 | }; 83 | 84 | // "+" 13x32 pixel segment font 85 | const uint8_t OLED_PLUS[] = { 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xFC, 0xFE, 0xFC, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 88 | 0x00, 0x00, 0x01, 0x01, 0x01, 0x1F, 0x3F, 0x1F, 0x01, 0x01, 0x01, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 90 | }; 91 | 92 | // =================================================================================== 93 | // OLED Update Function 94 | // =================================================================================== 95 | void OLED_update(void) { 96 | OLED_cursor(0, 0); 97 | 98 | // Display current volume gain level 99 | if(display) { 100 | OLED_drawBitmap((gain < 3 ? OLED_MINUS : OLED_PLUS), 13, 4); 101 | if (gain == 3) OLED_printSegment( 0, 4, 1, 0); 102 | else if((gain == 0) || (gain == 6)) OLED_printSegment(12, 4, 1, 0); 103 | else if((gain == 1) || (gain == 5)) OLED_printSegment( 8, 4, 1, 0); 104 | else OLED_printSegment( 4, 4, 1, 0); 105 | OLED_clearRect(21, 32); 106 | OLED_drawBitmap(OLED_DB, 29, 4); 107 | } 108 | 109 | // Display current transmitter frequency 110 | else { 111 | OLED_printSegment(freq, 4, 1, 1); 112 | OLED_clearRect(13, 32); 113 | OLED_drawBitmap(OLED_MHZ, 44, 4); 114 | } 115 | } 116 | 117 | // =================================================================================== 118 | // Button Read Function 119 | // =================================================================================== 120 | enum { KEY_NO, KEY_UP, KEY_OK, KEY_DOWN }; 121 | uint16_t KEY_ADC[] = { 850, 590, 250, 0 }; 122 | 123 | uint8_t KEY_read(void) { 124 | uint8_t ckey = 0; 125 | uint16_t ckeyval = ADC_read(); 126 | while(ckeyval < KEY_ADC[ckey]) ckey++; 127 | return ckey; 128 | } 129 | 130 | // =================================================================================== 131 | // Main Function 132 | // =================================================================================== 133 | int main(void) { 134 | // Lokal variables 135 | uint8_t i; 136 | uint8_t key; 137 | uint8_t keydelay; 138 | 139 | // Setup pins 140 | PIN_output(PIN_SW); 141 | PIN_output(PIN_RST); 142 | PIN_input_AN(PIN_KEYS); 143 | PIN_high(PIN_SW); 144 | PIN_high(PIN_RST); 145 | 146 | // Setup internal peripherals 147 | I2C_init(); 148 | ADC_init(); 149 | ADC_slow(); 150 | ADC_input(PIN_KEYS); 151 | 152 | // Setup external peripherals 153 | OLED_init(); 154 | OLED_clear(); 155 | OLED_update(); 156 | DLY_ms(500); 157 | KT_setFreq(freq); 158 | KT_setGain(gain); 159 | 160 | // Loop 161 | while(1) { 162 | // Read current key 163 | key = KEY_read(); 164 | 165 | // Volume gain display/control mode 166 | if(display) { 167 | switch(key) { 168 | case KEY_UP: if(gain < 6) KT_setGain(++gain); break; 169 | case KEY_DOWN: if(gain > 0) KT_setGain(--gain); break; 170 | case KEY_OK: display = 0; break; 171 | default: break; 172 | } 173 | if(key) { 174 | OLED_update(); 175 | while(KEY_read()); 176 | DLY_ms(10); 177 | } 178 | } 179 | 180 | // Transmitter frequency display/control mode 181 | else { 182 | switch(key) { 183 | case KEY_UP: freq++; break; 184 | case KEY_DOWN: freq--; break; 185 | case KEY_OK: display++; OLED_update(); while(KEY_read()); DLY_ms(10); break; 186 | default: keydelay = 50; break; 187 | } 188 | if(freq != KT_getFreq()) { 189 | if(freq < KT_FREQ_MIN) freq = KT_FREQ_MAX; 190 | if(freq > KT_FREQ_MAX) freq = KT_FREQ_MIN; 191 | KT_setFreq(freq); 192 | OLED_update(); 193 | i = keydelay; 194 | while((i--) && (KEY_read())) DLY_ms(10); 195 | keydelay = 5; 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/ssd1306_txt.c: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // SSD1306/SH1106/SH1107 I2C OLED Text Functions * v1.3 * 3 | // =================================================================================== 4 | // 5 | // Collection of the most necessary functions for controlling an SSD1306/SH1106 I2C 6 | // OLED for the display of simple text. 7 | // 8 | // References: 9 | // ----------- 10 | // - Neven Boyanov: https://github.com/tinusaur/ssd1306xled 11 | // - Stephen Denne: https://github.com/datacute/Tiny4kOLED 12 | // - David Johnson-Davies: http://www.technoblogy.com/show?TV4 13 | // - TinyOLEDdemo: https://github.com/wagiminator/attiny13-tinyoleddemo 14 | // - TinyTerminal: https://github.com/wagiminator/ATtiny85-TinyTerminal 15 | // - OLED Font Editor: http://sourpuss.net/projects/fontedit/ 16 | // 17 | // 2022 by Stefan Wagner: https://github.com/wagiminator 18 | 19 | #include "ssd1306_txt.h" 20 | 21 | // =================================================================================== 22 | // Standard ASCII 5x8 Font (chars 32 - 127) 23 | // =================================================================================== 24 | const uint8_t OLED_FONT[] = { 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 26 | 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 27 | 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x04, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, 28 | 0x00, 0x41, 0x22, 0x1C, 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, 0x08, 0x08, 0x3E, 0x08, 0x08, 29 | 0x00, 0x80, 0x60, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x60, 0x60, 0x00, 0x00, 30 | 0x20, 0x10, 0x08, 0x04, 0x02, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x44, 0x42, 0x7F, 0x40, 0x40, 31 | 0x42, 0x61, 0x51, 0x49, 0x46, 0x22, 0x41, 0x49, 0x49, 0x36, 0x18, 0x14, 0x12, 0x7F, 0x10, 32 | 0x2F, 0x49, 0x49, 0x49, 0x31, 0x3E, 0x49, 0x49, 0x49, 0x32, 0x03, 0x01, 0x71, 0x09, 0x07, 33 | 0x36, 0x49, 0x49, 0x49, 0x36, 0x26, 0x49, 0x49, 0x49, 0x3E, 0x00, 0x36, 0x36, 0x00, 0x00, 34 | 0x00, 0x80, 0x68, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 35 | 0x00, 0x22, 0x14, 0x08, 0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x55, 0x5E, 36 | 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, 37 | 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 38 | 0x3E, 0x41, 0x49, 0x49, 0x3A, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x41, 0x41, 0x7F, 0x41, 0x41, 39 | 0x20, 0x40, 0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 40 | 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 41 | 0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x41, 0xC1, 0xBE, 0x7F, 0x09, 0x19, 0x29, 0x46, 42 | 0x26, 0x49, 0x49, 0x49, 0x32, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x3F, 0x40, 0x40, 0x40, 0x3F, 43 | 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 44 | 0x07, 0x08, 0x70, 0x08, 0x07, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x00, 45 | 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x7F, 0x00, 0x08, 0x04, 0x02, 0x04, 0x08, 46 | 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x06, 0x09, 0x09, 0x06, 0x20, 0x54, 0x54, 0x54, 0x78, 47 | 0x7F, 0x44, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x44, 0x7F, 48 | 0x38, 0x54, 0x54, 0x54, 0x18, 0x08, 0xFE, 0x09, 0x01, 0x02, 0x18, 0xA4, 0xA4, 0xA4, 0x78, 49 | 0x7F, 0x04, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, 0x80, 0x84, 0x7D, 0x00, 50 | 0x41, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x7C, 0x04, 0x78, 51 | 0x7C, 0x04, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x24, 0x24, 0x24, 0x18, 52 | 0x18, 0x24, 0x24, 0x24, 0xFC, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x08, 0x54, 0x54, 0x54, 0x20, 53 | 0x04, 0x3F, 0x44, 0x40, 0x20, 0x3C, 0x40, 0x40, 0x40, 0x3C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 54 | 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x44, 0x28, 0x10, 0x28, 0x44, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C, 55 | 0x44, 0x64, 0x54, 0x4C, 0x44, 0x08, 0x08, 0x36, 0x41, 0x41, 0x00, 0x00, 0xFF, 0x00, 0x00, 56 | 0x41, 0x41, 0x36, 0x08, 0x08, 0x08, 0x04, 0x08, 0x10, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 57 | }; 58 | 59 | // =================================================================================== 60 | // 13x32 7-Segment Font (0 - 9) 61 | // =================================================================================== 62 | #if OLED_SEG_FONT == 1 63 | const uint8_t OLED_FONT_SEG[] = { 64 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 0 65 | 0x7F, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 66 | 0xFF, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 67 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0xFC, // 1 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1F, 72 | 0x00, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 2 73 | 0x00, 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 74 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 75 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x60, 0x40, 0x00, 76 | 0x00, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 3 77 | 0x00, 0x00, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 78 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 79 | 0x00, 0x40, 0x60, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 80 | 0xFC, 0xF8, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0xFC, // 4 81 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 82 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1F, 84 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0x00, // 5 85 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 87 | 0x00, 0x40, 0x60, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 88 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0x00, // 6 89 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 90 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 91 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 92 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 7 93 | 0x7F, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xFF, 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1F, 96 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 8 97 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 98 | 0xFF, 0xFE, 0xFC, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 99 | 0x1F, 0x4F, 0x67, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F, 100 | 0xFC, 0xF9, 0xF3, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xF3, 0xF9, 0xFC, // 9 101 | 0x7F, 0x3F, 0x9F, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x9F, 0x3F, 0x7F, 102 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFC, 0xFE, 0xFF, 103 | 0x00, 0x40, 0x60, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x67, 0x4F, 0x1F 104 | }; 105 | 106 | const uint8_t OLED_FONT_POINT[] = { 107 | 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 110 | 0x70, 0x70, 0x70 111 | }; 112 | #endif 113 | 114 | // =================================================================================== 115 | // 5x16 7-Segment Font (0 - 9) 116 | // =================================================================================== 117 | #if OLED_SEG_FONT == 2 118 | const uint8_t OLED_FONT_SEG[] = { 119 | 0x7C, 0x02, 0x02, 0x02, 0x7C, 0x1F, 0x20, 0x20, 0x20, 0x1F, // 0 120 | 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x1F, // 1 121 | 0x00, 0x82, 0x82, 0x82, 0x7C, 0x1F, 0x20, 0x20, 0x20, 0x00, // 2 122 | 0x00, 0x82, 0x82, 0x82, 0x7C, 0x00, 0x20, 0x20, 0x20, 0x1F, // 3 123 | 0x7C, 0x80, 0x80, 0x80, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x1F, // 4 124 | 0x7C, 0x82, 0x82, 0x82, 0x00, 0x00, 0x20, 0x20, 0x20, 0x1F, // 5 125 | 0x7C, 0x82, 0x82, 0x82, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x1F, // 6 126 | 0x7C, 0x02, 0x02, 0x02, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x1F, // 7 127 | 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x1F, 0x20, 0x20, 0x20, 0x1F, // 8 128 | 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00, 0x20, 0x20, 0x20, 0x1F // 9 129 | }; 130 | 131 | const uint8_t OLED_FONT_POINT[] = { 132 | 0x00, 0x00, 133 | 0x30, 0x30 134 | }; 135 | #endif 136 | 137 | // =================================================================================== 138 | // OLED Control Functions 139 | // =================================================================================== 140 | 141 | // Screen offsets 142 | #if OLED_SH1106 == 1 143 | #define OLED_XOFF ((128 - OLED_WIDTH) / 2) + 2 144 | #else 145 | #define OLED_XOFF ((128 - OLED_WIDTH) / 2) 146 | #endif 147 | 148 | // OLED initialisation sequence 149 | const uint8_t OLED_INIT_CMD[] = { 150 | OLED_MULTIPLEX, OLED_HEIGHT - 1, // set multiplex ratio 151 | OLED_CHARGEPUMP, 0x14, // set DC-DC enable 152 | OLED_MEMORYMODE, 0x00, // set horizontal addressing mode 153 | #if OLED_WIDTH == 128 && OLED_HEIGHT == 32 154 | OLED_COMPINS, 0x02, // set com pins 155 | #else 156 | OLED_COMPINS, 0x12, // set com pins 157 | #endif 158 | #if OLED_XFLIP > 0 159 | OLED_XFLIP_ON, // flip screen in X-direction 160 | #endif 161 | #if OLED_YFLIP > 0 162 | OLED_YFLIP_ON, // flip screen in Y-direction 163 | #endif 164 | #if OLED_INVERT > 0 165 | OLED_INVERT_ON, // invert screen 166 | #endif 167 | OLED_DISPLAY_ON // display on 168 | }; 169 | 170 | // OLED init function 171 | void OLED_init(void) { 172 | #if OLED_INIT_I2C > 0 173 | I2C_init(); // initialize I2C first 174 | #endif 175 | #if OLED_BOOT_TIME > 0 176 | DLY_ms(OLED_BOOT_TIME); // time for the OLED to boot up 177 | #endif 178 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 179 | I2C_write(OLED_CMD_MODE); // set command mode 180 | I2C_writeBuffer((uint8_t*)OLED_INIT_CMD, sizeof(OLED_INIT_CMD)); // send the command bytes 181 | } 182 | 183 | // Switch display on/off (0: display off, 1: display on) 184 | void OLED_display(uint8_t val) { 185 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 186 | I2C_write(OLED_CMD_MODE); // set command mode 187 | I2C_write(val ? OLED_DISPLAY_ON : OLED_DISPLAY_OFF); // set display power 188 | I2C_stop(); // stop transmission 189 | } 190 | 191 | // Set display contrast (0-255) 192 | void OLED_contrast(uint8_t val) { 193 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 194 | I2C_write(OLED_CMD_MODE); // set command mode 195 | I2C_write(OLED_CONTRAST); // contrast command 196 | I2C_write(val); // set contrast value 197 | I2C_stop(); // stop transmission 198 | } 199 | 200 | // Invert display (0: inverse off, 1: inverse on) 201 | void OLED_invert(uint8_t val) { 202 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 203 | I2C_write(OLED_CMD_MODE); // set command mode 204 | I2C_write(val ? OLED_INVERT_ON : OLED_INVERT_OFF); // set invert mode 205 | I2C_stop(); // stop transmission 206 | } 207 | 208 | // Flip display (0: flip off, 1: flip on) 209 | void OLED_flip(uint8_t xflip, uint8_t yflip) { 210 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 211 | I2C_write(OLED_CMD_MODE); // set command mode 212 | I2C_write(xflip ? OLED_XFLIP_ON : OLED_XFLIP_OFF); // set x-flip 213 | I2C_write(yflip ? OLED_YFLIP_ON : OLED_YFLIP_OFF); // set y-flip 214 | I2C_stop(); // stop transmission 215 | } 216 | 217 | // Scroll display vertically 218 | void OLED_vscroll(uint8_t y) { 219 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 220 | I2C_write(OLED_CMD_MODE); // set command mode 221 | I2C_write(OLED_OFFSET); // offset command 222 | I2C_write(y); // set y-scroll 223 | I2C_stop(); // stop transmission 224 | } 225 | 226 | // =================================================================================== 227 | // OLED Text Functions 228 | // =================================================================================== 229 | 230 | // OLED global variables 231 | uint8_t OLED_x, OLED_y, OLED_i; 232 | 233 | // OLED clear line 234 | void OLED_clearLine(uint8_t y) { 235 | uint8_t i; 236 | OLED_cursor(0, y); // set cursor to line start 237 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 238 | I2C_write(OLED_DAT_MODE); // set data mode 239 | for(i=OLED_WIDTH; i; i--) I2C_write(0x00); // clear line 240 | I2C_stop(); // stop transmission 241 | OLED_cursor(0, y); // re-set cursor to line start 242 | } 243 | 244 | // OLED clear screen 245 | void OLED_clear(void) { 246 | uint8_t y = OLED_HEIGHT / 8; 247 | while(y--) OLED_clearLine(y); // clear all lines 248 | } 249 | 250 | // OLED set cursor to specified position 251 | void OLED_cursor(uint8_t x, uint8_t y) { 252 | if(y >= OLED_HEIGHT / 8) y = 0; // limit y 253 | OLED_x = x; OLED_y = y; // set cursor variables 254 | x += OLED_XOFF; // add offset 255 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 256 | I2C_write(OLED_CMD_MODE); // set command mode 257 | I2C_write(OLED_PAGE + y); // set line 258 | I2C_write(x & 0xf); // set column 259 | I2C_write((x >> 4) | 0x10); 260 | I2C_stop(); // stop transmission 261 | } 262 | 263 | // OLED set text invert 264 | void OLED_textinvert(uint8_t yes) { 265 | OLED_i = yes; 266 | } 267 | 268 | #if OLED_BIGCHARS > 0 269 | 270 | // Character buffer 271 | uint8_t OLED_buf[2*10]; 272 | uint8_t OLED_sz; 273 | 274 | // Converts bit pattern abcdefgh into aabbccddeeffgghh 275 | uint16_t OLED_stretch(uint16_t x) { 276 | x = (x & 0xF0)<<4 | (x & 0x0F); 277 | x = (x<<2 | x) & 0x3333; 278 | x = (x<<1 | x) & 0x5555; 279 | return x | x<<1; 280 | } 281 | 282 | // Set character size 283 | void OLED_textsize(uint8_t size) { 284 | OLED_sz = size; 285 | } 286 | 287 | #endif // OLED_BIGCHARS > 0 288 | 289 | // OLED plot a single character 290 | void OLED_plotChar(char c) { 291 | uint16_t ptr = c - 32; // character pointer 292 | ptr += ptr << 2; // -> ptr = (ch - 32) * 5; 293 | #if OLED_BIGCHARS > 0 294 | if(OLED_sz == 0) { // normal character (5x8) 295 | #endif 296 | if(OLED_x > OLED_WIDTH - 6) OLED_cursor(0, OLED_y + 1); 297 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 298 | I2C_write(OLED_DAT_MODE); // set data mode 299 | I2C_write(OLED_i ? 0xff : 0x00); // write space between characters 300 | for(uint8_t i=5; i; i--) I2C_write(OLED_i ? ~OLED_FONT[ptr++] : OLED_FONT[ptr++]); 301 | I2C_stop(); 302 | OLED_x += 6; // move cursor 303 | #if OLED_BIGCHARS > 0 304 | } 305 | else if(OLED_sz == 1) { // v-stretched character (5x16) 306 | if(OLED_x > OLED_WIDTH - 6) OLED_cursor(0, OLED_y + 2); 307 | for(uint8_t i=0; i<5; i++) { 308 | uint16_t ch = OLED_stretch(OLED_FONT[ptr++]); 309 | OLED_buf[i] = ch; OLED_buf[i+5] = ch >> 8; 310 | } 311 | OLED_drawBitmap(OLED_buf, 5, 2); 312 | OLED_clearRect(1, 2); 313 | } 314 | else { // double-sized smoothed character (10x16) 315 | uint16_t col0L, col0R, col1L, col1R; // David Johnson-Davies' Smooth Big Text algorithm 316 | uint8_t col0 = OLED_FONT[ptr++]; 317 | if(OLED_x > OLED_WIDTH - 12) OLED_cursor(0, OLED_y + 2); 318 | col0L = OLED_stretch(col0); 319 | col0R = col0L; 320 | for(uint8_t col=0; col<10; col+=2) { 321 | uint8_t col1 = OLED_FONT[ptr++]; 322 | if(col == 8) col1 = 0; 323 | col1L = OLED_stretch(col1); 324 | col1R = col1L; 325 | for(int8_t i=6; i>=0; i--) { 326 | for(int8_t j=1; j<3; j++) { 327 | if(((col0>>i & 0b11) == (3 - j)) && ((col1>>i & 0b11) == j)) { 328 | col0R = col0R | 1<<((i << 1) + j); 329 | col1L = col1L | 1<<((i << 1) + 3 - j); 330 | } 331 | } 332 | } 333 | OLED_buf[col] = col0L; OLED_buf[col + 1] = col0R; 334 | OLED_buf[col + 10] = col0L >> 8; OLED_buf[col + 11] = col0R >> 8; 335 | col0 = col1; col0L = col1L; col0R = col1R; 336 | } 337 | OLED_drawBitmap(OLED_buf, 10, 2); 338 | OLED_clearRect(2, 2); 339 | } 340 | #endif 341 | } 342 | 343 | // OLED write a character or handle control characters 344 | void OLED_write(char c) { 345 | c &= 0x7f; // ignore top bit 346 | if(c >= 32) OLED_plotChar(c); // normal character 347 | #if OLED_BIGCHARS > 0 348 | else if(c == '\n') OLED_cursor(0, OLED_y + (OLED_sz ? 2 : 1)); 349 | #else 350 | else if(c == '\n') OLED_cursor(0, OLED_y + 1); // new line 351 | #endif 352 | else if(c == '\r') OLED_cursor(0, OLED_y); // carriage return 353 | } 354 | 355 | // OLED print a string 356 | void OLED_print(char* str) { 357 | while(*str) OLED_write(*str++); 358 | } 359 | 360 | // =================================================================================== 361 | // OLED Bitmap Functions 362 | // =================================================================================== 363 | 364 | // Draw bitmap (pointer *bmp) at cursor position width (w) in pixels, hight (h) in 8-pixel lines 365 | void OLED_drawBitmap(const uint8_t* bmp, uint8_t w, uint8_t h) { 366 | uint8_t y = OLED_y; 367 | while(h--) { 368 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 369 | I2C_write(OLED_DAT_MODE); // set data mode 370 | for(uint8_t i=w; i; i--) I2C_write(OLED_i ? ~(*bmp++) : *bmp++); 371 | I2C_stop(); 372 | OLED_cursor(OLED_x, OLED_y + 1); // set next line 373 | } 374 | OLED_cursor(OLED_x + w, y); // move cursor 375 | } 376 | 377 | // =================================================================================== 378 | // OLED 7-Segment Functions 379 | // =================================================================================== 380 | 381 | // Clear a rectangle starting from cursor position 382 | void OLED_clearRect(uint8_t w, uint8_t h) { 383 | uint8_t y = OLED_y; 384 | while(h--) { 385 | I2C_start(OLED_ADDR << 1); // start transmission to OLED 386 | I2C_write(OLED_DAT_MODE); // set data mode 387 | for(uint8_t i=w; i; i--) I2C_write(OLED_i ? 0xff : 0x00); // clear line 388 | I2C_stop(); // stop transmission 389 | OLED_cursor(OLED_x, OLED_y + 1); // set next line 390 | } 391 | OLED_cursor(OLED_x + w, y); // move cursor 392 | } 393 | 394 | // Print value as 7-segment digits (BCD conversion by substraction method) 395 | void OLED_printSegment(uint16_t value, uint8_t digits, uint8_t lead, uint8_t decimal) { 396 | static const uint16_t DIVIDER[] = {1, 10, 100, 1000, 10000}; 397 | uint8_t leadflag = 0; // flag for leading spaces 398 | while(digits--) { // for all digits digits 399 | uint8_t digitval = 0; // start with digit value 0 400 | uint16_t divider = DIVIDER[digits]; // read current divider 401 | while(value >= divider) { // if current divider fits into the value 402 | leadflag = 1; // end of leading spaces 403 | digitval++; // increase digit value 404 | value -= divider; // decrease value by divider 405 | } 406 | if(digits == decimal) leadflag++; // end leading characters before decimal 407 | if(leadflag || !lead) { 408 | #if OLED_SEG_FONT == 0 409 | OLED_write(digitval + '0'); 410 | #elif OLED_SEG_FONT == 1 411 | uint16_t ptr = (uint16_t)digitval; // character pointer 412 | ptr = (ptr << 5) + (ptr << 4) + (ptr << 2); // -> ptr = c * 13 * 4; 413 | OLED_drawBitmap((uint8_t*)&OLED_FONT_SEG[ptr], 13, 4); 414 | #elif OLED_SEG_FONT == 2 415 | uint16_t ptr = (uint16_t)digitval; // character pointer 416 | ptr = (ptr << 3) + (ptr << 1); // -> ptr = c * 5 * 2; 417 | OLED_drawBitmap((uint8_t*)&OLED_FONT_SEG[ptr], 5, 2); 418 | #endif 419 | } 420 | else { 421 | #if OLED_SEG_FONT == 0 422 | OLED_write(' '); 423 | #elif OLED_SEG_FONT == 1 424 | OLED_clearRect(13, 4); 425 | #elif OLED_SEG_FONT == 2 426 | OLED_clearRect( 5, 2); 427 | #endif 428 | } 429 | #if OLED_SEG_FONT == 1 430 | OLED_clearRect(OLED_SEG_SPACE, 4); 431 | #elif OLED_SEG_FONT == 2 432 | OLED_clearRect(OLED_SEG_SPACE, 2); 433 | #endif 434 | if(decimal && (digits == decimal)) { 435 | #if OLED_SEG_FONT == 0 436 | OLED_write('.'); 437 | #elif OLED_SEG_FONT == 1 438 | OLED_drawBitmap(OLED_FONT_POINT, 3, 4); 439 | OLED_clearRect(OLED_SEG_SPACE, 4); 440 | #elif OLED_SEG_FONT == 2 441 | OLED_drawBitmap(OLED_FONT_POINT, 2, 2); 442 | OLED_clearRect(OLED_SEG_SPACE, 2); 443 | #endif 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/ssd1306_txt.h: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // SSD1306/SH1106/SH1107 I2C OLED Text Functions * v1.3 * 3 | // =================================================================================== 4 | // 5 | // Collection of the most necessary functions for controlling an SSD1306/SH1106 I2C 6 | // OLED for the display of simple text, working without a screen buffer. 7 | // 8 | // Functions available: 9 | // -------------------- 10 | // OLED_init() Init OLED display 11 | // OLED_display(v) Switch display on/off (0: display off, 1: display on) 12 | // OLED_contrast(v) Set OLED contrast (0-255) 13 | // OLED_invert(v) Invert display (0: inverse off, 1: inverse on) 14 | // OLED_flip(xflip,yflip) Flip display (0: flip off, 1: flip on) 15 | // OLED_vscroll(y) Scroll display vertically (0-64) 16 | // OLED_clear() Clear screen of OLED display 17 | // OLED_clearLine(y) Clear line y 18 | // 19 | // OLED_cursor(x,y) Set text cursor at position (x,y) 20 | // OLED_textsize(sz) Set text size (0: 5x8, 1: 5x16, 2: 10x16), enable OLED_BIGCHARS 21 | // OLED_textinvert(v) Invert text (0: inverse off, 1: inverse on) 22 | // OLED_write(c) Write character at cursor position or handle control characters 23 | // OLED_print(str) Print string (*str) at cursor position 24 | // OLED_printSegment(v,d,l,dp) Print value (v) at cursor position using defined segment font 25 | // with (d) number of digits, (l) leading (0: '0', 1: space) and 26 | // decimal point at position (dp) counted from the right 27 | // OLED_drawBitmap(bmp,w,h) Draw bitmap (pointer *bmp) at cursor position 28 | // width (w) in pixels, hight (h) in 8-pixel lines 29 | // 30 | // If print functions are activated (see below, print.h must be included): 31 | // ----------------------------------------------------------------------- 32 | // OLED_printf(f, ...) printf (supports %s, %c, %d, %u, %x, %b, %02d, %%) 33 | // OLED_printD(n) Print decimal value 34 | // OLED_printW(n) Print 32-bit hex word value 35 | // OLED_printH(n) Print 16-bit hex half-word value 36 | // OLED_printB(n) Print 8-bit hex byte value 37 | // OLED_printS(s) Print string 38 | // OLED_println(s) Print string with newline 39 | // OLED_newline() Send newline 40 | // 41 | // Tested devices: 42 | // --------------- 43 | // - 1.5" 128x128 SH1107 44 | // - 1.3" 128x64 SH1106 45 | // - 0.96" 128x64 SSD1306 46 | // - 0.91" 128x32 SSD1306 47 | // - 0.49" 64x32 SSD1306 48 | // - 0.42" 72x40 SSD1306 49 | // 50 | // References: 51 | // ----------- 52 | // - Neven Boyanov: https://github.com/tinusaur/ssd1306xled 53 | // - Stephen Denne: https://github.com/datacute/Tiny4kOLED 54 | // - David Johnson-Davies: http://www.technoblogy.com/show?TV4 55 | // - TinyOLEDdemo: https://github.com/wagiminator/attiny13-tinyoleddemo 56 | // - TinyTerminal: https://github.com/wagiminator/ATtiny85-TinyTerminal 57 | // - OLED Font Editor: http://sourpuss.net/projects/fontedit/ 58 | // 59 | // 2022 by Stefan Wagner: https://github.com/wagiminator 60 | 61 | #pragma once 62 | 63 | #ifdef __cplusplus 64 | extern "C" { 65 | #endif 66 | 67 | #include "i2c_tx.h" // choose your I2C library 68 | #include "system.h" 69 | 70 | // OLED Parameters 71 | #define OLED_ADDR 0x3C // OLED I2C device address 72 | #define OLED_WIDTH 128 // OLED width in pixels 73 | #define OLED_HEIGHT 32 // OLED height in pixels 74 | #define OLED_SH1106 0 // OLED driver - 0: SSD1306/SH1107, 1: SH1106 75 | 76 | #define OLED_BOOT_TIME 50 // OLED boot up time in milliseconds 77 | #define OLED_INIT_I2C 0 // 1: init I2C with OLED_init() 78 | #define OLED_XFLIP 1 // 1: flip screen in X-direction with OLED_init() 79 | #define OLED_YFLIP 1 // 1: flip screen in Y-direction with OLED_init() 80 | #define OLED_INVERT 0 // 1: invert screen with OLED_init() 81 | 82 | // OLED Text Settings 83 | #define OLED_PRINT 0 // 1: include print functions (needs print.h) 84 | #define OLED_BIGCHARS 0 // 1: use big fonts (OLED_textsize()) 85 | #define OLED_SEG_FONT 1 // 0: standard font, 1: 13x32 digits, 2: 5x16 digits 86 | #define OLED_SEG_SPACE 3 // width of space between segment digits in pixels 87 | 88 | // OLED Modes 89 | #define OLED_CMD_MODE 0x00 // set command mode 90 | #define OLED_DAT_MODE 0x40 // set data mode 91 | #define OLED_CMD_ONCE 0x80 // send one command byte 92 | #define OLED_DAT_ONCE 0xC0 // send one data byte 93 | 94 | // OLED Commands 95 | #define OLED_COLUMN_LOW 0x00 // set lower 4 bits of start column (0x00 - 0x0F) 96 | #define OLED_COLUMN_HIGH 0x10 // set higher 4 bits of start column (0x10 - 0x1F) 97 | #define OLED_MEMORYMODE 0x20 // set memory addressing mode (following byte) 98 | #define OLED_COLUMNS 0x21 // set start and end column (following 2 bytes) 99 | #define OLED_PAGES 0x22 // set start and end page (following 2 bytes) 100 | #define OLED_STARTLINE 0x40 // set display start line (0x40-0x7F = 0-63) 101 | #define OLED_CONTRAST 0x81 // set display contrast (following byte, 0-255) 102 | #define OLED_CHARGEPUMP 0x8D // (following byte - 0x14:enable, 0x10: disable) 103 | #define OLED_XFLIP_OFF 0xA0 // don't flip display horizontally 104 | #define OLED_XFLIP_ON 0xA1 // flip display horizontally 105 | #define OLED_RESUME 0xA4 // display all on resume 106 | #define OLED_ALL_ON 0xA5 // display all on 107 | #define OLED_INVERT_OFF 0xA6 // set non-inverted display 108 | #define OLED_INVERT_ON 0xA7 // set inverse display 109 | #define OLED_MULTIPLEX 0xA8 // set multiplex ratio (following byte) 110 | #define OLED_DISPLAY_OFF 0xAE // set display off (sleep mode) 111 | #define OLED_DISPLAY_ON 0xAF // set display on 112 | #define OLED_PAGE 0xB0 // set start page (0xB0-0xB7 = 0-7) 113 | #define OLED_YFLIP_OFF 0xC0 // don't flip display vertically 114 | #define OLED_YFLIP_ON 0xC8 // flip display vertically 115 | #define OLED_OFFSET 0xD3 // set display offset (y-scroll: following byte) 116 | #define OLED_CLOCK 0xD5 // set frequency (bits 7-4) and divider (bits 3-0) 117 | #define OLED_PRECHARGE 0xD9 // set pre-charge period (following byte) 118 | #define OLED_COMPINS 0xDA // set COM pin config (following byte) 119 | #define OLED_VCOM_DETECT 0xDB // set VCOM detect (following byte) 120 | 121 | // OLED Control Functions 122 | void OLED_init(void); // OLED init function 123 | void OLED_display(uint8_t val); // Switch display on/off (0: display off, 1: display on) 124 | void OLED_contrast(uint8_t val); // Set display contrast (0-255) 125 | void OLED_invert(uint8_t val); // Invert display (0: inverse off, 1: inverse on) 126 | void OLED_flip(uint8_t xflip, uint8_t yflip); // Flip display (0: flip off, 1: flip on) 127 | void OLED_vscroll(uint8_t y); // Scroll display vertically (0-64) 128 | 129 | // OLED Text Functions 130 | void OLED_clear(void); // Clear screen 131 | void OLED_clearLine(uint8_t y); // Clear line y 132 | void OLED_write(char c); // Write a character or handle control characters 133 | void OLED_print(char* str); // Print a string 134 | void OLED_cursor(uint8_t x, uint8_t y); // Set cursor 135 | void OLED_textinvert(uint8_t yes); // Invert text 136 | 137 | #if OLED_BIGCHARS > 0 138 | void OLED_textsize(uint8_t size); // Set text size (0: 5x8, 1: 5x16, 2: 10x16) 139 | #endif 140 | 141 | // OLED Special Functions 142 | void OLED_drawBitmap(const uint8_t* bmp, uint8_t w, uint8_t h); 143 | void OLED_clearRect(uint8_t w, uint8_t h); 144 | void OLED_printSegment(uint16_t value, uint8_t digits, uint8_t lead, uint8_t decimal); 145 | 146 | #define OLED_textcolor(c) OLED_textinvert(!(c)) 147 | 148 | // OLED Cursor Position 149 | extern uint8_t OLED_x, OLED_y; 150 | 151 | // Additional print functions (if activated, see above) 152 | #if OLED_PRINT == 1 153 | #include "print.h" 154 | #define OLED_printD(n) printD(OLED_write, n) // print decimal as string 155 | #define OLED_printW(n) printW(OLED_write, n) // print word as string 156 | #define OLED_printH(n) printH(OLED_write, n) // print half-word as string 157 | #define OLED_printB(n) printB(OLED_write, n) // print byte as string 158 | #define OLED_printS(s) printS(OLED_write, s) // print string 159 | #define OLED_println(s) println(OLED_write, s) // print string with newline 160 | #define OLED_newline() OLED_write('\n') // send newline 161 | #define OLED_printf(f, ...) printF(OLED_write, f, ##__VA_ARGS__) 162 | #endif 163 | 164 | #ifdef __cplusplus 165 | }; 166 | #endif 167 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/system.c: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic System Functions for CH32V003 * v1.6 * 3 | // =================================================================================== 4 | // 5 | // This file must be included!!!! 6 | // 7 | // References: 8 | // ----------- 9 | // - CNLohr ch32v003fun: https://github.com/cnlohr/ch32v003fun 10 | // - WCH Nanjing Qinheng Microelectronics: http://wch.cn 11 | // 12 | // 2023 by Stefan Wagner: https://github.com/wagiminator 13 | 14 | #include "system.h" 15 | 16 | // =================================================================================== 17 | // Setup Microcontroller (this function is called automatically at startup) 18 | // =================================================================================== 19 | void SYS_init(void) { 20 | // Init system clock 21 | #if SYS_CLK_INIT > 0 22 | #if F_CPU > 24000000 23 | FLASH->ACTLR = FLASH_ACTLR_LATENCY_1; // 1 cycle latency 24 | #endif 25 | CLK_init(); // init system clock 26 | #endif 27 | 28 | // Init SYSTICK 29 | #if SYS_TICK_INIT > 0 30 | STK_init(); 31 | #endif 32 | 33 | // Enable GPIO 34 | #if SYS_GPIO_EN > 0 35 | RCC->APB2PCENR |= RCC_IOPAEN | RCC_IOPCEN | RCC_IOPDEN; 36 | #endif 37 | } 38 | 39 | // =================================================================================== 40 | // System Clock Functions 41 | // =================================================================================== 42 | 43 | // Init internal oscillator (non PLL) as system clock source 44 | void CLK_init_HSI(void) { 45 | RCC->CFGR0 = CLK_DIV; // set clock divider 46 | } 47 | 48 | // Init internal oscillator with PLL as system clock source 49 | void CLK_init_HSI_PLL(void) { 50 | RCC->CTLR = RCC_HSION | RCC_PLLON | ((HSITRIM) << 3); // enable PLL, keep HSI on 51 | while(!(RCC->CTLR & RCC_PLLRDY)); // wait till PLL is ready 52 | RCC->CFGR0 = CLK_DIV | RCC_SW_PLL; // select PLL as system clock source 53 | while((RCC->CFGR0 & RCC_SWS) != RCC_SWS_PLL); // wait till PLL is used as system clock source 54 | } 55 | 56 | // Init external crystal (non PLL) as system clock source 57 | void CLK_init_HSE(void) { 58 | RCC->APB2PCENR |= RCC_AFIOEN; // enable auxiliary clock module 59 | AFIO->PCFR1 |= AFIO_PCFR1_PA12_REMAP; // pins PA1-PA2 for external crystal 60 | RCC->CTLR = RCC_HSION | RCC_HSEON | ((HSITRIM) << 3); // enable HSE and keep HSI on 61 | while(!(RCC->CTLR & RCC_HSERDY)); // wait till HSE is ready 62 | RCC->CFGR0 = CLK_DIV | RCC_SW_HSE; // set clock divider, use HSE for system clock 63 | while((RCC->CFGR0 & RCC_SWS) != RCC_SWS_HSE); // wait till HSE is used as system clock source 64 | } 65 | 66 | // Init external crystal (PLL) as system clock source 67 | void CLK_init_HSE_PLL(void) { 68 | RCC->APB2PCENR |= RCC_AFIOEN; // enable auxiliary clock module 69 | AFIO->PCFR1 |= AFIO_PCFR1_PA12_REMAP; // pins PA1-PA2 for external crystal 70 | RCC->CTLR = RCC_HSION | RCC_HSEON | ((HSITRIM) << 3); // enable HSE and keep HSI on 71 | while(!(RCC->CTLR & RCC_HSERDY)); // wait till HSE is ready 72 | RCC->CFGR0 = RCC_PLLSRC | CLK_DIV; // set clock divider, use HSE as PLL source 73 | RCC->CTLR = RCC_PLLON | RCC_HSION | RCC_HSEON | ((HSITRIM) << 3); // enable PLL 74 | while(!(RCC->CTLR & RCC_PLLRDY)); // wait till PLL is ready 75 | RCC->CFGR0 = RCC_PLLSRC | CLK_DIV | RCC_SW_PLL; // select PLL as system clock source 76 | while((RCC->CFGR0 & RCC_SWS) != RCC_SWS_PLL); // wait till PLL is used as system clock source 77 | } 78 | 79 | // Reset system clock to default state 80 | void CLK_reset(void) { 81 | RCC->CTLR |= RCC_HSION; // enable HSI 82 | while(!(RCC->CTLR & RCC_HSIRDY)); // wait until HSI is ready 83 | RCC->CFGR0 = 0x00000000; // select HSI as system clock source 84 | while(RCC->CFGR0 & RCC_SWS); // wait until HSI is selected 85 | RCC->CTLR = RCC_HSION | ((HSITRIM) << 3); // use HSI only 86 | RCC->INTR = 0x009F0000; // disable interrupts and clear flags 87 | FLASH->ACTLR = FLASH_ACTLR_LATENCY_0; // no flash wait states 88 | } 89 | 90 | // Setup pin PC4 for MCO (output, push-pull, 50MHz, auxiliary) 91 | void MCO_init(void) { 92 | RCC->APB2PCENR |= RCC_AFIOEN | RCC_IOPCEN; 93 | GPIOC->CFGLR = (GPIOC->CFGLR & ~((uint32_t)0b1111<<(4<<2))) | ((uint32_t)0b1011<<(4<<2)); 94 | } 95 | 96 | // =================================================================================== 97 | // Delay Functions 98 | // =================================================================================== 99 | 100 | // Wait n counts of SysTick 101 | void DLY_ticks(uint32_t n) { 102 | uint32_t end = STK->CNT + n; 103 | while(((int32_t)(STK->CNT - end)) < 0); 104 | } 105 | 106 | // =================================================================================== 107 | // Bootloader (BOOT) Functions 108 | // =================================================================================== 109 | 110 | // Perform software reset and jump to bootloader 111 | void BOOT_now(void) { 112 | FLASH->KEYR = 0x45670123; 113 | FLASH->KEYR = 0xCDEF89AB; 114 | FLASH->BOOT_MODEKEYR = 0x45670123; 115 | FLASH->BOOT_MODEKEYR = 0xCDEF89AB; // unlock flash 116 | FLASH->STATR |= (uint16_t)1<<14; // start bootloader after software reset 117 | FLASH->CTLR |= FLASH_CTLR_LOCK; // lock flash 118 | RCC->RSTSCKR |= RCC_RMVF; // clear reset flags 119 | PFIC->CFGR = PFIC_RESETSYS | PFIC_KEY3; // perform software reset 120 | } 121 | 122 | // =================================================================================== 123 | // Independent Watchdog Timer (IWDG) Functions 124 | // =================================================================================== 125 | 126 | // Start independent watchdog timer (IWDG) with given time in milliseconds (max 8191). 127 | // Once the IWDG has been started, it cannot be disabled, only reloaded (feed). 128 | // It can be stopped by disabling the internal low-speed clock (LSI). 129 | void IWDG_start(uint16_t ms) { 130 | LSI_enable(); // enable internal low-speed clock (LSI) 131 | IWDG->CTLR = 0x5555; // allow register modification 132 | while(IWDG->STATR & IWDG_PVU); // wait for clock register to be ready 133 | IWDG->PSCR = 0b111; // set LSI clock prescaler 256 134 | while(IWDG->STATR & IWDG_RVU); // wait for reload register to be ready 135 | IWDG->RLDR = ms >> 1; // set watchdog counter reload value 136 | IWDG->CTLR = 0xAAAA; // load reload value into watchdog counter 137 | IWDG->CTLR = 0xCCCC; // enable IWDG 138 | } 139 | 140 | // Reload watchdog counter with n milliseconds, n<=8191 141 | void IWDG_reload(uint16_t ms) { 142 | IWDG->CTLR = 0x5555; // allow register modification 143 | while(IWDG->STATR & IWDG_RVU); // wait for reload register to be ready 144 | IWDG->RLDR = ms >> 1; // set watchdog counter reload value 145 | IWDG->CTLR = 0xAAAA; // load reload value into watchdog counter 146 | } 147 | 148 | // =================================================================================== 149 | // Automatic Wake-up Timer (AWU) Functions 150 | // =================================================================================== 151 | 152 | // Init automatic wake-up timer 153 | void AWU_init(void) { 154 | LSI_enable(); // enable internal low-speed clock (LSI) 155 | EXTI->EVENR |= ((uint32_t)1<<9); // enable AWU event 156 | EXTI->RTENR |= ((uint32_t)1<<9); // enable AWU rising edge triggering 157 | RCC->APB1PCENR |= RCC_PWREN; // enable power module 158 | PWR->AWUCSR = PWR_AWUCSR_AWUEN; // enable automatic wake-up timer 159 | } 160 | 161 | // Stop automatic wake-up timer 162 | void AWU_stop(void) { 163 | PWR->AWUCSR = 0x00; // disable automatic wake-up timer 164 | EXTI->EVENR &= ~((uint32_t)1<<9); // disable AWU event 165 | EXTI->RTENR &= ~((uint32_t)1<<9); // disable AWU rising edge triggering 166 | } 167 | 168 | // =================================================================================== 169 | // Sleep Functions 170 | // =================================================================================== 171 | 172 | // Put device into sleep, wake up by interrupt 173 | void SLEEP_WFI_now(void) { 174 | PFIC->SCTLR &= ~PFIC_SLEEPDEEP; // set power-down mode to sleep 175 | __WFI(); // wait for interrupt 176 | } 177 | 178 | // Put device into sleep, wake up by event 179 | void SLEEP_WFE_now(void) { 180 | PFIC->SCTLR &= ~PFIC_SLEEPDEEP; // set power-down mode to sleep 181 | __WFE(); // wait for event 182 | } 183 | 184 | // Put device into standby (deep sleep), wake up interrupt 185 | void STDBY_WFI_now(void) { 186 | RCC->APB1PCENR |= RCC_PWREN; // enable power module 187 | PWR->CTLR |= PWR_CTLR_PDDS; // set power-down mode to standby (deep sleep) 188 | PFIC->SCTLR |= PFIC_SLEEPDEEP; 189 | __WFI(); // wait for interrupt 190 | PWR->CTLR &= ~PWR_CTLR_PDDS; // disable PDDS again 191 | } 192 | 193 | // Put device into standby (deep sleep), wake up event 194 | void STDBY_WFE_now(void) { 195 | RCC->APB1PCENR |= RCC_PWREN; // enable power module 196 | PWR->CTLR |= PWR_CTLR_PDDS; // set power-down mode to standby (deep sleep) 197 | PFIC->SCTLR |= PFIC_SLEEPDEEP; 198 | __WFE(); // wait for event 199 | PWR->CTLR &= ~PWR_CTLR_PDDS; // disable PDDS again 200 | } 201 | 202 | // =================================================================================== 203 | // C++ Support 204 | // Based on CNLohr ch32v003fun: https://github.com/cnlohr/ch32v003fun 205 | // =================================================================================== 206 | #ifdef __cplusplus 207 | extern void __cxa_pure_virtual() { while (1); } 208 | extern void (*__preinit_array_start[]) (void) __attribute__((weak)); 209 | extern void (*__preinit_array_end[]) (void) __attribute__((weak)); 210 | extern void (*__init_array_start[]) (void) __attribute__((weak)); 211 | extern void (*__init_array_end[]) (void) __attribute__((weak)); 212 | 213 | void __libc_init_array(void) { 214 | uint32_t count, i; 215 | count = __preinit_array_end - __preinit_array_start; 216 | for(i = 0; i < count; i++) __preinit_array_start[i](); 217 | count = __init_array_end - __init_array_start; 218 | for(i = 0; i < count; i++) __init_array_start[i](); 219 | } 220 | #endif 221 | 222 | // =================================================================================== 223 | // C version of CH32V003 Startup .s file from WCH 224 | // Based on CNLohr ch32v003fun: https://github.com/cnlohr/ch32v003fun 225 | // =================================================================================== 226 | extern uint32_t _sbss; 227 | extern uint32_t _ebss; 228 | extern uint32_t _data_lma; 229 | extern uint32_t _data_vma; 230 | extern uint32_t _edata; 231 | 232 | // Prototypes 233 | int main(void) __attribute__((section(".text.main"), used)); 234 | void jump_reset(void) __attribute__((section(".init.jump"), naked, used)); 235 | void reset_handler(void) __attribute__((section(".text.reset_handler"), naked, used)); 236 | 237 | // FLASH starts with a jump to the reset handler 238 | void jump_reset(void) { asm volatile("j reset_handler"); } 239 | 240 | #if SYS_USE_VECTORS > 0 241 | // Unless a specific handler is overridden, it just spins forever 242 | void default_handler(void) __attribute__((section(".text.vector_handler"), naked, used)); 243 | void default_handler(void) { while(1); } 244 | 245 | // All interrupt handlers are aliased to default_handler unless overridden individually 246 | #define DUMMY_HANDLER __attribute__((section(".text.vector_handler"), weak, alias("default_handler"), used)) 247 | DUMMY_HANDLER void NMI_Handler(void); 248 | DUMMY_HANDLER void HardFault_Handler(void); 249 | DUMMY_HANDLER void SysTick_Handler(void); 250 | DUMMY_HANDLER void SW_Handler(void); 251 | DUMMY_HANDLER void WWDG_IRQHandler(void); 252 | DUMMY_HANDLER void PVD_IRQHandler(void); 253 | DUMMY_HANDLER void FLASH_IRQHandler(void); 254 | DUMMY_HANDLER void RCC_IRQHandler(void); 255 | DUMMY_HANDLER void EXTI7_0_IRQHandler(void); 256 | DUMMY_HANDLER void AWU_IRQHandler(void); 257 | DUMMY_HANDLER void DMA1_Channel1_IRQHandler(void); 258 | DUMMY_HANDLER void DMA1_Channel2_IRQHandler(void); 259 | DUMMY_HANDLER void DMA1_Channel3_IRQHandler(void); 260 | DUMMY_HANDLER void DMA1_Channel4_IRQHandler(void); 261 | DUMMY_HANDLER void DMA1_Channel5_IRQHandler(void); 262 | DUMMY_HANDLER void DMA1_Channel6_IRQHandler(void); 263 | DUMMY_HANDLER void DMA1_Channel7_IRQHandler(void); 264 | DUMMY_HANDLER void ADC1_IRQHandler(void); 265 | DUMMY_HANDLER void I2C1_EV_IRQHandler(void); 266 | DUMMY_HANDLER void I2C1_ER_IRQHandler(void); 267 | DUMMY_HANDLER void USART1_IRQHandler(void); 268 | DUMMY_HANDLER void SPI1_IRQHandler(void); 269 | DUMMY_HANDLER void TIM1_BRK_IRQHandler(void); 270 | DUMMY_HANDLER void TIM1_UP_IRQHandler(void); 271 | DUMMY_HANDLER void TIM1_TRG_COM_IRQHandler(void); 272 | DUMMY_HANDLER void TIM1_CC_IRQHandler(void); 273 | DUMMY_HANDLER void TIM2_IRQHandler(void); 274 | 275 | // Interrupt vector table 276 | void (*const vectors[])(void) __attribute__((section(".init.vectors"), used)); 277 | void (*const vectors[])(void) = { 278 | // RISC-V handlers 279 | 0, // 1 - Reserved 280 | NMI_Handler, // 2 - NMI Handler 281 | HardFault_Handler, // 3 - Hard Fault Handler 282 | 0, // 4 - Reserved 283 | 0, // 5 - Reserved 284 | 0, // 6 - Reserved 285 | 0, // 7 - Reserved 286 | 0, // 8 - Reserved 287 | 0, // 9 - Reserved 288 | 0, // 10 - Reserved 289 | 0, // 11 - Reserved 290 | SysTick_Handler, // 12 - SysTick Handler 291 | 0, // 13 - Reserved 292 | SW_Handler, // 14 - SW Handler 293 | 0, // 15 - Reserved 294 | 295 | // Peripheral handlers 296 | WWDG_IRQHandler, // 16 - Window Watchdog 297 | PVD_IRQHandler, // 17 - PVD through EXTI Line detect 298 | FLASH_IRQHandler, // 18 - Flash 299 | RCC_IRQHandler, // 19 - RCC 300 | EXTI7_0_IRQHandler, // 20 - EXTI Line 7..0 301 | AWU_IRQHandler, // 21 - AWU 302 | DMA1_Channel1_IRQHandler, // 22 - DMA1 Channel 1 303 | DMA1_Channel2_IRQHandler, // 23 - DMA1 Channel 2 304 | DMA1_Channel3_IRQHandler, // 24 - DMA1 Channel 3 305 | DMA1_Channel4_IRQHandler, // 25 - DMA1 Channel 4 306 | DMA1_Channel5_IRQHandler, // 26 - DMA1 Channel 5 307 | DMA1_Channel6_IRQHandler, // 27 - DMA1 Channel 6 308 | DMA1_Channel7_IRQHandler, // 28 - DMA1 Channel 7 309 | ADC1_IRQHandler, // 29 - ADC1 310 | I2C1_EV_IRQHandler, // 30 - I2C1 Event 311 | I2C1_ER_IRQHandler, // 31 - I2C1 Error 312 | USART1_IRQHandler, // 32 - USART1 313 | SPI1_IRQHandler, // 33 - SPI1 314 | TIM1_BRK_IRQHandler, // 34 - TIM1 Break 315 | TIM1_UP_IRQHandler, // 35 - TIM1 Update 316 | TIM1_TRG_COM_IRQHandler, // 36 - TIM1 Trigger and Commutation 317 | TIM1_CC_IRQHandler, // 37 - TIM1 Capture Compare 318 | TIM2_IRQHandler, // 38 - TIM2 319 | }; 320 | #endif // SYS_USE_VECTORS > 0 321 | 322 | // Reset handler 323 | void reset_handler(void) { 324 | uint32_t *src, *dst; 325 | 326 | // Set pointers, vectors, processor status, and interrupts 327 | asm volatile( 328 | " .option push \n\ 329 | .option norelax \n\ 330 | la gp, __global_pointer$ \n\ 331 | .option pop \n\ 332 | la sp, _eusrstack \n" 333 | #if __GNUC__ > 10 334 | ".option arch, +zicsr \n" 335 | #endif 336 | " li a0, 0x88 \n\ 337 | csrw mstatus, a0 \n\ 338 | li a1, 0x3 \n\ 339 | csrw 0x804, a1 \n\ 340 | la a0, jump_reset \n\ 341 | or a0, a0, a1 \n\ 342 | csrw mtvec, a0 \n\ 343 | csrw mepc, %[main] \n" 344 | : : [main] "r" (main) : "a0", "a1" , "memory" 345 | ); 346 | 347 | // Copy data from FLASH to RAM 348 | src = &_data_lma; 349 | dst = &_data_vma; 350 | while(dst < &_edata) *dst++ = *src++; 351 | 352 | // Clear uninitialized variables 353 | #if SYS_CLEAR_BSS > 0 354 | dst = &_sbss; 355 | while(dst < &_ebss) *dst++ = 0; 356 | #endif 357 | 358 | // C++ Support 359 | #ifdef __cplusplus 360 | __libc_init_array(); 361 | #endif 362 | 363 | // Init system 364 | SYS_init(); 365 | 366 | // Return 367 | asm volatile("mret"); 368 | } 369 | -------------------------------------------------------------------------------- /software/fm_transmitter/src/system.h: -------------------------------------------------------------------------------- 1 | // =================================================================================== 2 | // Basic System Functions for CH32V003 * v1.6 * 3 | // =================================================================================== 4 | // 5 | // This file must be included!!! The system configuration and the system clock are 6 | // set up automatically on system start. 7 | // 8 | // System clock functions available: 9 | // --------------------------------- 10 | // CLK_init_HSI() init internal oscillator (non PLL) as system clock source 11 | // CLK_init_HSI_PLL() init internal oscillator with PLL as system clock source 12 | // CLK_init_HSE() init external crystal (non PLL) as system clock source 13 | // CLK_init_HSE_PLL() init external crystal (PLL) as system clock source 14 | // CLK_reset() reset system clock to default state 15 | // 16 | // HSI_enable() enable internal 8MHz high-speed clock (HSI) 17 | // HSI_disable() disable HSI 18 | // HSI_ready() check if HSI is stable 19 | // 20 | // HSE_enable() enable external high-speed clock (HSE) 21 | // HSE_disable() disable HSE 22 | // HSE_ready() check if HSE is stable 23 | // HSE_bypass_on() enable HSE clock bypass 24 | // HSE_bypass_off() disable HSE clock bypass 25 | // 26 | // LSI_enable() enable internal 128kHz low-speed clock (LSI) 27 | // LSI_disable() disable LSI 28 | // LSI_ready() check if LSI is stable 29 | // 30 | // PLL_enable() enable phase-locked loop (PLL) 31 | // PLL_disable() disable PLL 32 | // PLL_ready() check if PLL is stable 33 | // PLL_setHSI() set HSI as PLL input (PLL muste be disabled) 34 | // PLL_setHSE() set HSE as PLL input (PLL muste be disabled) 35 | // 36 | // MCO_init() init clock output to pin PC4 37 | // MCO_setSYS() output SYS_CLK on pin PC4 38 | // MCO_setHSI() output internal oscillator on pin PC4 39 | // MCO_setHSE() output external crystal on pin PC4 (if available) 40 | // MCO_setPLL() output PLL on pin PC4 41 | // 42 | // Delay (DLY) functions available: 43 | // -------------------------------- 44 | // DLY_ticks(n) delay n clock cycles 45 | // DLY_us(n) delay n microseconds 46 | // DLY_ms(n) delay n milliseconds 47 | // 48 | // Reset (RST) and Bootloader (BOOT) functions available: 49 | // ------------------------------------------------------ 50 | // BOOT_now() conduct software reset and jump to bootloader 51 | // RST_now() conduct software reset 52 | // RST_clearFlags() clear all reset flags 53 | // RST_wasLowPower() check if last reset was caused by low power 54 | // RST_wasWWDG() check if last reset was caused by window watchdog 55 | // RST_wasIWDG() check if last reset was caused by independent watchdog 56 | // RST_wasSoftware() check if last reset was caused by software 57 | // RST_wasPower() check if last reset was caused by power up 58 | // RST_wasPin() check if last reset was caused by RST pin low 59 | // 60 | // Independent Watchdog Timer (IWDG) functions available: 61 | // ------------------------------------------------------ 62 | // IWDG_start(n) start independent watchdog timer, n milliseconds, n<=8191 63 | // IWDG_reload(n) reload watchdog counter with n milliseconds, n<=8191 64 | // IWDG_feed() feed the dog (reload last time) 65 | // 66 | // Automatic Wake-up Timer (AWU) functions available: 67 | // -------------------------------------------------- 68 | // AWU_start(n) start AWU with n milliseconds period and event trigger 69 | // AWU_stop() stop AWU and event trigger 70 | // AWU_set(n) set AWU period to n milliseconds 71 | // 72 | // AWU_enable() enable AWU (without LSI and PWR module) 73 | // AWU_disable() disable AWU (without LSI and PWR module) 74 | // AWU_RT_enable() enable AWU rising edge trigger 75 | // AWU_RT_disable() disable AWU rising edge trigger 76 | // AWU_EV_enable() enable AWU event 77 | // AWU_EV_disable() disable AWU event 78 | // AWU_INT_enable() enable AWU interrupt (without NVIC) 79 | // AWU_INT_disable() disable AWU interrupt (without NVIC) 80 | // 81 | // Sleep functions available: 82 | // -------------------------- 83 | // SLEEP_WFI_now() put device into sleep, wake up by interrupt 84 | // SLEEP_WFE_now() put device into sleep, wake up by event 85 | // STDBY_WFI_now() put device into standby (deep sleep), wake by interrupt 86 | // STDBY_WFE_now() put device into standby (deep sleep), wake by event 87 | // 88 | // SLEEP_ms(n) put device into SLEEP for n milliseconds (uses AWU) 89 | // STDBY_ms(n) put device into STANDBY for n milliseconds (uses AWU) 90 | // 91 | // Programmable Voltage Detector (PVD) functions available: 92 | // -------------------------------------------------------- 93 | // PVD_enable() enable PVD 94 | // PVD_set_2V7() set detection level to 2.7V falling / 2.85V rising edge 95 | // PVD_set_2V9() set detection level to 2.9V falling / 3.05V rising edge 96 | // PVD_set_3V15() set detection level to 3.15V falling / 3.3V rising edge 97 | // PVD_set_3V3() set detection level to 3.3V falling / 3.5V rising edge 98 | // PVD_set_3V5() set detection level to 3.5V falling / 3.7V rising edge 99 | // PVD_set_3V7() set detection level to 3.7V falling / 3.9V rising edge 100 | // PVD_set_3V9() set detection level to 3.9V falling / 4.1V rising edge 101 | // PVD_set_4V2() set detection level to 4.2V falling / 4.4V rising edge 102 | // PVD_isLow() check if VDD is below detection level 103 | // 104 | // Interrupt (INT) functions available: 105 | // ------------------------------------ 106 | // INT_enable() global interrupt enable 107 | // INT_disable() global interrupt disable 108 | // INT_ATOMIC_BLOCK { } execute block without being interrupted 109 | // 110 | // References: 111 | // ----------- 112 | // - CNLohr ch32v003fun: https://github.com/cnlohr/ch32v003fun 113 | // - WCH Nanjing Qinheng Microelectronics: http://wch.cn 114 | // 115 | // 2023 by Stefan Wagner: https://github.com/wagiminator 116 | 117 | #pragma once 118 | 119 | #ifdef __cplusplus 120 | extern "C" { 121 | #endif 122 | 123 | #include "ch32v003.h" 124 | 125 | // =================================================================================== 126 | // System Options (set "1" to activate) 127 | // =================================================================================== 128 | #define SYS_CLK_INIT 1 // 1: init system clock on startup 129 | #define SYS_TICK_INIT 1 // 1: init and start SYSTICK on startup 130 | #define SYS_GPIO_EN 1 // 1: enable GPIO ports on startup 131 | #define SYS_CLEAR_BSS 1 // 1: clear uninitialized variables 132 | #define SYS_USE_VECTORS 0 // 1: create interrupt vector table 133 | #define SYS_USE_HSE 0 // 1: use external crystal 134 | 135 | // =================================================================================== 136 | // Sytem Clock Defines 137 | // =================================================================================== 138 | // Set system clock frequency 139 | #ifndef F_CPU 140 | #define F_CPU 24000000 // 24Mhz if not otherwise defined 141 | #endif 142 | 143 | // Calculate system clock settings 144 | #if F_CPU == 48000000 145 | #define CLK_DIV RCC_HPRE_DIV1 146 | #define SYS_USE_PLL 147 | #elif F_CPU == 24000000 148 | #define CLK_DIV RCC_HPRE_DIV1 149 | #elif F_CPU == 16000000 150 | #define CLK_DIV RCC_HPRE_DIV3 151 | #define SYS_USE_PLL 152 | #elif F_CPU == 12000000 153 | #define CLK_DIV RCC_HPRE_DIV2 154 | #elif F_CPU == 8000000 155 | #define CLK_DIV RCC_HPRE_DIV3 156 | #elif F_CPU == 6000000 157 | #define CLK_DIV RCC_HPRE_DIV4 158 | #elif F_CPU == 4000000 159 | #define CLK_DIV RCC_HPRE_DIV6 160 | #elif F_CPU == 3000000 161 | #define CLK_DIV RCC_HPRE_DIV8 162 | #elif F_CPU == 1500000 163 | #define CLK_DIV RCC_HPRE_DIV16 164 | #elif F_CPU == 750000 165 | #define CLK_DIV RCC_HPRE_DIV32 166 | #elif F_CPU == 375000 167 | #define CLK_DIV RCC_HPRE_DIV64 168 | #elif F_CPU == 187500 169 | #define CLK_DIV RCC_HPRE_DIV128 170 | #elif F_CPU == 93750 171 | #define CLK_DIV RCC_HPRE_DIV256 172 | #else 173 | #warning Unsupported system clock frequency, using internal 24MHz 174 | #define CLK_DIV RCC_HPRE_DIV1 175 | #undef F_CPU 176 | #define F_CPU 24000000 177 | #endif 178 | 179 | #if SYS_USE_HSE > 0 180 | #ifdef SYS_USE_PLL 181 | #define CLK_init CLK_init_HSE_PLL 182 | #else 183 | #define CLK_init CLK_init_HSE 184 | #endif 185 | #else 186 | #ifdef SYS_USE_PLL 187 | #define CLK_init CLK_init_HSI_PLL 188 | #else 189 | #define CLK_init CLK_init_HSI 190 | #endif 191 | #endif 192 | 193 | // =================================================================================== 194 | // System Clock Functions 195 | // =================================================================================== 196 | void CLK_init_HSI(void); // init internal oscillator (non PLL) as system clock source 197 | void CLK_init_HSI_PLL(void); // init internal oscillator with PLL as system clock source 198 | void CLK_init_HSE(void); // init external crystal (non PLL) as system clock source 199 | void CLK_init_HSE_PLL(void); // init external crystal (PLL) as system clock source 200 | void CLK_reset(void); // reset system clock to default state 201 | 202 | // Internal 8MHz high-speed clock (HSI) functions 203 | #define HSI_enable() RCC->CTLR |= RCC_HSION // enable HSI 204 | #define HSI_disable() RCC->CTLR &= ~RCC_HSION // disable HSI 205 | #define HSI_ready() (RCC->CTLR & RCC_HSIRDY) // check if HSI is stable 206 | 207 | // External high-speed clock (HSE) functions 208 | #define HSE_enable() RCC->CTLR |= RCC_HSEON // enable HSE 209 | #define HSE_disable() RCC->CTLR &= ~RCC_HSEON // disable HSE 210 | #define HSE_ready() (RCC->CTLR & RCC_HSERDY) // check if HSE is stable 211 | #define HSE_bypass_on() RCC->CTLR |= RCC_HSEBYP // enable HSE clock bypass 212 | #define HSE_bypass_off() RCC->CTLR &= ~RCC_HSEBYP // disable HSE clock bypass 213 | 214 | // Internal 128kHz low-speed clock (LSI) functions 215 | #define LSI_enable() RCC->RSTSCKR |= RCC_LSION // enable LSI 216 | #define LSI_disable() RCC->RSTSCKR &= ~RCC_LSION // disable LSI 217 | #define LSI_ready() (RCC->RSTSCKR & RCC_LSIRDY) // check if LSI is stable 218 | 219 | // Phase-locked loop (PLL) functions 220 | #define PLL_enable() RCC->CTLR |= RCC_PLLON // enable PLL 221 | #define PLL_disable() RCC->CTLR &= ~RCC_PLLON // disable PLL 222 | #define PLL_ready() (RCC->CTLR & RCC_PLLRDY) // check if PLL is stable 223 | #define PLL_setHSI() RCC->CFGR0 &= ~RCC_PLLSRC // set HSI as PLL input 224 | #define PLL_setHSE() RCC->CFGR0 |= RCC_PLLSRC // set HSE as PLL input 225 | 226 | // Clock output functions (pin PC4) 227 | #define MCO_setSYS() RCC->CFGR0 = (RCC->CFGR0 & ~RCC_CFGR0_MCO) | RCC_CFGR0_MCO_SYSCLK 228 | #define MCO_setHSI() RCC->CFGR0 = (RCC->CFGR0 & ~RCC_CFGR0_MCO) | RCC_CFGR0_MCO_HSI 229 | #define MCO_setHSE() RCC->CFGR0 = (RCC->CFGR0 & ~RCC_CFGR0_MCO) | RCC_CFGR0_MCO_HSE 230 | #define MCO_setPLL() RCC->CFGR0 = (RCC->CFGR0 & ~RCC_CFGR0_MCO) | RCC_CFGR0_MCO_PLL 231 | #define MCO_stop() RCC->CFGR0 &= ~RCC_CFGR0_MCO // stop clock output to pin PC4 232 | void MCO_init(void); // init clock output to pin PC4 233 | 234 | // =================================================================================== 235 | // Delay (DLY) Functions 236 | // =================================================================================== 237 | #define STK_init() STK->CTLR = STK_CTLR_STE | STK_CTLR_STCLK // init SYSTICK @ F_CPU 238 | #define DLY_US_TIME (F_CPU / 1000000) // system ticks per us 239 | #define DLY_MS_TIME (F_CPU / 1000) // system ticks per ms 240 | #define DLY_us(n) DLY_ticks((n) * DLY_US_TIME) // delay n microseconds 241 | #define DLY_ms(n) DLY_ticks((n) * DLY_MS_TIME) // delay n milliseconds 242 | void DLY_ticks(uint32_t n); // delay n system ticks 243 | 244 | // =================================================================================== 245 | // Reset (RST) Functions 246 | // =================================================================================== 247 | #define RST_now() PFIC->CFGR = PFIC_RESETSYS | PFIC_KEY3 248 | #define RST_clearFlags() RCC->RSTSCKR |= RCC_RMVF 249 | #define RST_wasLowPower() (RCC->RSTSCKR & RCC_LPWRRSTF) 250 | #define RST_wasWWDG() (RCC->RSTSCKR & RCC_WWDGRSTF) 251 | #define RST_wasIWDG() (RCC->RSTSCKR & RCC_IWDGRSTF) 252 | #define RST_wasSoftware() (RCC->RSTSCKR & RCC_SFTRSTF) 253 | #define RST_wasPower() (RCC->RSTSCKR & RCC_PORRSTF) 254 | #define RST_wasPin() (RCC->RSTSCKR & RCC_PINRSTF) 255 | 256 | // =================================================================================== 257 | // Bootloader (BOOT) Functions 258 | // =================================================================================== 259 | void BOOT_now(void); // perform software reset and jump to bootloader 260 | 261 | // =================================================================================== 262 | // Independent Watchdog Timer (IWDG) Functions 263 | // =================================================================================== 264 | void IWDG_start(uint16_t ms); // start IWDG with time in ms 265 | void IWDG_reload(uint16_t ms); // reload IWDG with time in ms 266 | #define IWDG_feed() IWDG->CTLR = 0xAAAA // feed the dog (reload time) 267 | #define IWDG_reset() IWDG->CTLR = 0xAAAA // alias 268 | 269 | // =================================================================================== 270 | // Automatic Wake-up Timer (AWU) Functions 271 | // =================================================================================== 272 | void AWU_init(void); // init automatic wake-up timer 273 | void AWU_stop(void); // stop automatic wake-up timer 274 | 275 | // AWU macros 276 | #define AWU_start(n) {AWU_init(); AWU_set(n);} 277 | #define AWU_enable() PWR->AWUCSR = PWR_AWUCSR_AWUEN 278 | #define AWU_disable() PWR->AWUCSR = 0x00 279 | #define AWU_RT_enable() EXTI->RTENR |= ((uint32_t)1 << 9) 280 | #define AWU_RT_disable() EXTI->RTENR &= ~((uint32_t)1 << 9) 281 | #define AWU_EV_enable() EXTI->EVENR |= ((uint32_t)1 << 9) 282 | #define AWU_EV_disable() EXTI->EVENR &= ~((uint32_t)1 << 9) 283 | #define AWU_INT_enable() EXTI->INTENR |= ((uint32_t)1 << 9) 284 | #define AWU_INT_disable() EXTI->INTENR &= ~((uint32_t)1 << 9) 285 | 286 | #define AWU_sleep(ms) {AWU_set(ms); SLEEP_WFE_now();} 287 | #define AWU_stdby(ms) {AWU_set(ms); STDBY_WFE_now();} 288 | 289 | // Set automatic wake-up timer in milliseconds 290 | #define AWU_set(ms) \ 291 | (ms < 64 ? ({PWR->AWUPSC = 0b1000; PWR->AWUWR = (ms); }) : \ 292 | (ms < 128 ? ({PWR->AWUPSC = 0b1001; PWR->AWUWR = (ms)>>1; }) : \ 293 | (ms < 256 ? ({PWR->AWUPSC = 0b1010; PWR->AWUWR = (ms)>>2; }) : \ 294 | (ms < 512 ? ({PWR->AWUPSC = 0b1011; PWR->AWUWR = (ms)>>3; }) : \ 295 | (ms < 1024 ? ({PWR->AWUPSC = 0b1100; PWR->AWUWR = (ms)>>4; }) : \ 296 | (ms < 2048 ? ({PWR->AWUPSC = 0b1101; PWR->AWUWR = (ms)>>5; }) : \ 297 | (ms < 5120 ? ({PWR->AWUPSC = 0b1110; PWR->AWUWR = (ms)/80; }) : \ 298 | (ms < 30720 ? ({PWR->AWUPSC = 0b1111; PWR->AWUWR = (ms)/480;}) : \ 299 | (0))))))))) 300 | 301 | // =================================================================================== 302 | // Sleep Functions 303 | // =================================================================================== 304 | void SLEEP_WFI_now(void); // put device into sleep, wake up by interrupt 305 | void SLEEP_WFE_now(void); // put device into sleep, wake up by event 306 | void STDBY_WFI_now(void); // put device into standby (deep sleep), wake up interrupt 307 | void STDBY_WFE_now(void); // put device into standby (deep sleep), wake up event 308 | 309 | #define SLEEP_ms(n) {AWU_start(n); SLEEP_WFE_now(); AWU_stop();} 310 | #define STDBY_ms(n) {AWU_start(n); STDBY_WFE_now(); AWU_stop();} 311 | 312 | // =================================================================================== 313 | // Programmable Voltage Detector (PVD) Functions 314 | // =================================================================================== 315 | #define PVD_enable() {RCC->APB1PCENR |= RCC_PWREN; PWR->CTLR |= PWR_CTLR_PVDE;} 316 | #define PVD_set_2V7() PWR->CTLR &= ~PWR_CTLR_PLS 317 | #define PVD_set_2V9() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b001 << 5) 318 | #define PVD_set_3V15() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b010 << 5) 319 | #define PVD_set_3V3() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b011 << 5) 320 | #define PVD_set_3V5() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b100 << 5) 321 | #define PVD_set_3V7() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b101 << 5) 322 | #define PVD_set_3V9() PWR->CTLR = (PWR->CTLR & ~PWR_CTLR_PLS) | (0b110 << 5) 323 | #define PVD_set_4V2() PWR->CTLR |= PWR_CTLR_PLS 324 | #define PVD_isLow() (PWR->CSR & PWR_CSR_PVDO) 325 | 326 | #define PVD_RT_enable() EXTI->RTENR |= ((uint32_t)1 << 8) 327 | #define PVD_RT_disable() EXTI->RTENR &= ~((uint32_t)1 << 8) 328 | #define PVD_FT_enable() EXTI->FTENR |= ((uint32_t)1 << 8) 329 | #define PVD_FT_disable() EXTI->FTENR &= ~((uint32_t)1 << 8) 330 | #define PVD_EV_enable() EXTI->EVENR |= ((uint32_t)1 << 8) 331 | #define PVD_EV_disable() EXTI->EVENR &= ~((uint32_t)1 << 8) 332 | #define PVD_INT_enable() EXTI->INTENR |= ((uint32_t)1 << 8) 333 | #define PVD_INT_disable() EXTI->INTENR &= ~((uint32_t)1 << 8) 334 | 335 | // =================================================================================== 336 | // Interrupt (INT) Functions 337 | // =================================================================================== 338 | #define INT_enable() __enable_irq() 339 | #define INT_disable() __disable_irq() 340 | #define INT_ATOMIC_BLOCK for(INT_ATOMIC_RESTORE, __ToDo = 1; __ToDo; __ToDo = 0) 341 | #define INT_ATOMIC_RESTORE uint32_t __reg_save __attribute__((__cleanup__(__iRestore))) = __iSave() 342 | 343 | // Save interrupt status and disable interrupts 344 | static inline uint32_t __iSave(void) { 345 | uint32_t result, temp; 346 | __asm volatile( 347 | #if __GNUC__ > 10 348 | ".option arch, +zicsr \n" 349 | #endif 350 | "csrr %0, mstatus \n" 351 | "andi %1, %0, ~0x88 \n" 352 | "csrw mstatus, %1 \n" 353 | "andi %0, %0, 0x88" : "=r" (result), "=r" (temp) 354 | ); 355 | return result; 356 | } 357 | 358 | // Restore interrupt status 359 | static inline void __iRestore(const uint32_t *__s) { 360 | uint32_t temp; 361 | __asm volatile( 362 | #if __GNUC__ > 10 363 | ".option arch, +zicsr \n" 364 | #endif 365 | "csrr %0, mstatus \n" 366 | "or %0, %0, %1 \n" 367 | "csrw mstatus, %0" : "=&r" (temp) : "r" (*__s) 368 | ); 369 | } 370 | 371 | // =================================================================================== 372 | // Device Electronic Signature (ESIG) 373 | // =================================================================================== 374 | #define ESIG_FLASHSIZE (*(__I uint16_t*)(0x1FFFF7E0)) 375 | #define ESIG_UID1 (*(__I uint32_t*)(0x1FFFF7E8)) 376 | #define ESIG_UID2 (*(__I uint32_t*)(0x1FFFF7EC)) 377 | #define ESIG_UID3 (*(__I uint32_t*)(0x1FFFF7F0)) 378 | 379 | // =================================================================================== 380 | // Imported System Functions 381 | // =================================================================================== 382 | // Enable Global Interrupt 383 | static inline void __enable_irq(void) { 384 | uint32_t temp; 385 | __asm volatile( 386 | #if __GNUC__ > 10 387 | ".option arch, +zicsr \n" 388 | #endif 389 | "csrr %0, mstatus \n" 390 | "ori %0, %0, 0x88 \n" 391 | "csrw mstatus, %0" : "=r" (temp) 392 | ); 393 | } 394 | 395 | // Disable Global Interrupt 396 | static inline void __disable_irq(void) { 397 | uint32_t temp; 398 | __asm volatile( 399 | #if __GNUC__ > 10 400 | ".option arch, +zicsr \n" 401 | #endif 402 | "csrr %0, mstatus \n" 403 | "andi %0, %0, ~0x88 \n" 404 | "csrw mstatus, %0" : "=r" (temp) 405 | ); 406 | } 407 | 408 | // No OPeration 409 | static inline void __NOP(void) { 410 | __asm volatile("nop"); 411 | } 412 | 413 | // Enable NVIC interrupt (interrupt numbers) 414 | static inline void NVIC_EnableIRQ(IRQn_Type IRQn) { 415 | NVIC->IENR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 416 | } 417 | 418 | // Disable NVIC interrupt (interrupt numbers) 419 | static inline void NVIC_DisableIRQ(IRQn_Type IRQn) { 420 | NVIC->IRER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 421 | } 422 | 423 | // Get Interrupt Enable State 424 | static inline uint32_t NVIC_GetStatusIRQ(IRQn_Type IRQn) { 425 | return((uint32_t) ((NVIC->ISR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0)); 426 | } 427 | 428 | // Get Interrupt Pending State 429 | static inline uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) { 430 | return((uint32_t) ((NVIC->IPR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0)); 431 | } 432 | 433 | // Set Interrupt Pending 434 | static inline void NVIC_SetPendingIRQ(IRQn_Type IRQn) { 435 | NVIC->IPSR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 436 | } 437 | 438 | // Clear Interrupt Pending 439 | static inline void NVIC_ClearPendingIRQ(IRQn_Type IRQn) { 440 | NVIC->IPRR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); 441 | } 442 | 443 | // Get Interrupt Active State 444 | static inline uint32_t NVIC_GetActive(IRQn_Type IRQn) { 445 | return((uint32_t)((NVIC->IACTR[(uint32_t)(IRQn) >> 5] & (1 << ((uint32_t)(IRQn) & 0x1F)))?1:0)); 446 | } 447 | 448 | // Set Interrupt Priority 449 | static inline void NVIC_SetPriority(IRQn_Type IRQn, uint8_t priority) { 450 | NVIC->IPRIOR[(uint32_t)(IRQn)] = priority; 451 | } 452 | 453 | // Wait for Interrupt 454 | __attribute__( ( always_inline ) ) static inline void __WFI(void) { 455 | NVIC->SCTLR &= ~(1<<3); // wfi 456 | asm volatile ("wfi"); 457 | } 458 | 459 | // Wait for Events 460 | __attribute__( ( always_inline ) ) static inline void __WFE(void) { 461 | uint32_t t; 462 | t = NVIC->SCTLR; 463 | NVIC->SCTLR |= (1<<3)|(1<<5); // (wfi->wfe)+(__sev) 464 | NVIC->SCTLR = (NVIC->SCTLR & ~(1<<5)) | ( t & (1<<5)); 465 | asm volatile ("wfi"); 466 | asm volatile ("wfi"); 467 | } 468 | 469 | // Set VTF Interrupt 470 | static inline void SetVTFIRQ(uint32_t addr, IRQn_Type IRQn, uint8_t num, FunctionalState NewState) { 471 | if(num > 1) return; 472 | if(NewState != DISABLE) { 473 | NVIC->VTFIDR[num] = IRQn; 474 | NVIC->VTFADDR[num] = ((addr&0xFFFFFFFE)|0x1); 475 | } 476 | else { 477 | NVIC->VTFIDR[num] = IRQn; 478 | NVIC->VTFADDR[num] = ((addr&0xFFFFFFFE)&(~0x1)); 479 | } 480 | } 481 | 482 | // Initiate a system reset request 483 | static inline void NVIC_SystemReset(void) { 484 | NVIC->CFGR = NVIC_KEY3|(1<<7); 485 | } 486 | 487 | // Return the Machine Status Register 488 | static inline uint32_t __get_MSTATUS(void) { 489 | uint32_t result; 490 | __ASM volatile( 491 | #if __GNUC__ > 10 492 | ".option arch, +zicsr \n" 493 | #endif 494 | "csrr %0, mstatus" : "=r" (result) 495 | ); 496 | return (result); 497 | } 498 | 499 | // Set the Machine Status Register 500 | static inline void __set_MSTATUS(uint32_t value) { 501 | __ASM volatile( 502 | #if __GNUC__ > 10 503 | ".option arch, +zicsr \n" 504 | #endif 505 | "csrw mstatus, %0" : : "r" (value) 506 | ); 507 | } 508 | 509 | // Return the Machine ISA Register 510 | static inline uint32_t __get_MISA(void) { 511 | uint32_t result; 512 | __ASM volatile( 513 | #if __GNUC__ > 10 514 | ".option arch, +zicsr \n" 515 | #endif 516 | "csrr %0, misa" : "=r" (result) 517 | ); 518 | return (result); 519 | } 520 | 521 | // Set the Machine ISA Register 522 | static inline void __set_MISA(uint32_t value) { 523 | __ASM volatile( 524 | #if __GNUC__ > 10 525 | ".option arch, +zicsr \n" 526 | #endif 527 | "csrw misa, %0" : : "r" (value) 528 | ); 529 | } 530 | 531 | // Return the Machine Trap-Vector Base-Address Register 532 | static inline uint32_t __get_MTVEC(void) { 533 | uint32_t result; 534 | __ASM volatile( 535 | #if __GNUC__ > 10 536 | ".option arch, +zicsr \n" 537 | #endif 538 | "csrr %0, mtvec" : "=r" (result) 539 | ); 540 | return (result); 541 | } 542 | 543 | // Set the Machine Trap-Vector Base-Address Register 544 | static inline void __set_MTVEC(uint32_t value) { 545 | __ASM volatile( 546 | #if __GNUC__ > 10 547 | ".option arch, +zicsr \n" 548 | #endif 549 | "csrw mtvec, %0" : : "r" (value) 550 | ); 551 | } 552 | 553 | // Return the Machine Seratch Register 554 | static inline uint32_t __get_MSCRATCH(void) { 555 | uint32_t result; 556 | __ASM volatile( 557 | #if __GNUC__ > 10 558 | ".option arch, +zicsr \n" 559 | #endif 560 | "csrr %0, mscratch" : "=r" (result) 561 | ); 562 | return (result); 563 | } 564 | 565 | // Set the Machine Seratch Register 566 | static inline void __set_MSCRATCH(uint32_t value) { 567 | __ASM volatile( 568 | #if __GNUC__ > 10 569 | ".option arch, +zicsr \n" 570 | #endif 571 | "csrw mscratch, %0" : : "r" (value) 572 | ); 573 | } 574 | 575 | // Return the Machine Exception Program Register 576 | static inline uint32_t __get_MEPC(void) { 577 | uint32_t result; 578 | __ASM volatile( 579 | #if __GNUC__ > 10 580 | ".option arch, +zicsr \n" 581 | #endif 582 | "csrr %0, mepc" : "=r" (result) 583 | ); 584 | return (result); 585 | } 586 | 587 | // Set the Machine Exception Program Register 588 | static inline void __set_MEPC(uint32_t value) { 589 | __ASM volatile( 590 | #if __GNUC__ > 10 591 | ".option arch, +zicsr \n" 592 | #endif 593 | "csrw mepc, %0" : : "r" (value) 594 | ); 595 | } 596 | 597 | // Return the Machine Cause Register 598 | static inline uint32_t __get_MCAUSE(void) { 599 | uint32_t result; 600 | __ASM volatile( 601 | #if __GNUC__ > 10 602 | ".option arch, +zicsr \n" 603 | #endif 604 | "csrr %0, mcause" : "=r" (result) 605 | ); 606 | return (result); 607 | } 608 | 609 | // Set the Machine Cause Register 610 | static inline void __set_MCAUSE(uint32_t value) { 611 | __ASM volatile( 612 | #if __GNUC__ > 10 613 | ".option arch, +zicsr \n" 614 | #endif 615 | "csrw mcause, %0" : : "r" (value) 616 | ); 617 | } 618 | 619 | // Return Vendor ID Register 620 | static inline uint32_t __get_MVENDORID(void) { 621 | uint32_t result; 622 | __ASM volatile( 623 | #if __GNUC__ > 10 624 | ".option arch, +zicsr \n" 625 | #endif 626 | "csrr %0, mvendorid" : "=r" (result) 627 | ); 628 | return (result); 629 | } 630 | 631 | // Return Machine Architecture ID Register 632 | static inline uint32_t __get_MARCHID(void) { 633 | uint32_t result; 634 | __ASM volatile( 635 | #if __GNUC__ > 10 636 | ".option arch, +zicsr \n" 637 | #endif 638 | "csrr %0, marchid" : "=r" (result) 639 | ); 640 | return (result); 641 | } 642 | 643 | // Return Machine Implementation ID Register 644 | static inline uint32_t __get_MIMPID(void) { 645 | uint32_t result; 646 | __ASM volatile( 647 | #if __GNUC__ > 10 648 | ".option arch, +zicsr \n" 649 | #endif 650 | "csrr %0, mimpid" : "=r" (result) 651 | ); 652 | return (result); 653 | } 654 | 655 | // Return Hart ID Register 656 | static inline uint32_t __get_MHARTID(void) { 657 | uint32_t result; 658 | __ASM volatile( 659 | #if __GNUC__ > 10 660 | ".option arch, +zicsr \n" 661 | #endif 662 | "csrr %0, mhartid" : "=r" (result) 663 | ); 664 | return (result); 665 | } 666 | 667 | // Return SP Register 668 | static inline uint32_t __get_SP(void) { 669 | uint32_t result; 670 | __ASM volatile("mv %0, sp" : "=r" (result):); 671 | return (result); 672 | } 673 | 674 | #ifdef __cplusplus 675 | }; 676 | #endif 677 | --------------------------------------------------------------------------------