For HF and amateur VHF bands our WiFi VFO works great.
But we didn't have a cost-effective UHF signal source until now…
Thanks to Ismo (OH2FTG), we recently experimented with a HopeRF CMT2219A powered board called HOPERF RF module RFM119W-433S1
.
Here is the CMT2219A powered board in action producing a CW (OOK) signal at ~433 MHz.
The stability is pretty good and a bit surprising considering that the board uses a 20ppm 26 MHz regular quartz crystal - not bad!
The official RFPDK software is pretty easy to use and runs fine without the external (and expensive) USB programmer connected.
Future work: Create a 5W UHF RF amplifier for fun and learning purposes!
Here is the MCU code targeting Raspberry Pi Pico using the arduino-pico
framework:
/*
* CMT2119A OOK CW Transmitter
*
* For Raspberry Pi Pico with Arduino-Pico framework
*
* Message: "CQ CQ CQ DE VU3CER"
*
* Reference: github.com/g4eml/RP2040_Synth
*/
// Pin definitions (matching folder 1 code)
#define TWICLK 6 // GPIO 6 Connect to CMT2119A CLK Pin
#define TWIDAT 7 // GPIO 7 Connect to CMT2119A DAT Pin
// Frequency configuration
#define CARRIER_FREQ_MHZ 250.00 // Set your desired frequency in MHz (150-1297 MHz range)
#define REF_OSC_MHZ 26.0 // CMT2119A reference oscillator
// Timing for morse code (WPM = 12)
#define DOT_MS 100
#define DASH_MS 300
#define SYMBOL_SPACE_MS 100
#define CHAR_SPACE_MS 300
#define WORD_SPACE_MS 700
double refOsc = 26.0;
/*
Generated using RFPDK 1.63
https://hoperf.com/service/information/tool/?key=RFPDK
;---------------------------------------
; CMT2119A Configuration File
; Configuration File
; 2025.10.04 16:44
;---------------------------------------
; Mode = Advanced
; Part Number = CMT2119A
; Frequency = 433.50 MHz
; Modulation = OOK
; Symbol Rate = 0.5-30.0 ksps
; Tx Power = +10 dBm
; Deviation = NA
; PA Ramping Time = 1024 us
; Xtal Cload = 15.00 pF
; Data Representation = NA
; Tx Start by = DATA Pin Rising Edge
; Tx Stop by = DATA Pin Holding Low For 20 ms
; Increase XO Current = No
; FILE CRC = F334
*/
static const uint16_t CMT2119ook[21] = {
0x007F,
0x5400,
0x0000,
0x0000,
0x0000,
0xF000,
0x0000,
0xB13B,
0x4200,
0x0000,
0x2401,
0x01B0,
0x82BA,
0x000D,
0xFFFF,
0x0020,
0x5FCE,
0x22D6,
0x0E13,
0x0019,
0x2000,
};
// Morse code patterns
typedef struct {
uint8_t len;
uint8_t pattern;
} morse_t;
static const morse_t morse_table[36] = {
// A-Z
{ 2, 0b10 }, // A .-
{ 4, 0b1000 }, // B -...
{ 4, 0b1010 }, // C -.-.
{ 3, 0b100 }, // D -..
{ 1, 0b0 }, // E .
{ 4, 0b0010 }, // F ..-.
{ 3, 0b110 }, // G --.
{ 4, 0b0000 }, // H ....
{ 2, 0b00 }, // I ..
{ 4, 0b0111 }, // J .---
{ 3, 0b101 }, // K -.-
{ 4, 0b0100 }, // L .-..
{ 2, 0b11 }, // M --
{ 2, 0b10 }, // N -.
{ 3, 0b111 }, // O ---
{ 4, 0b0110 }, // P .--.
{ 4, 0b1101 }, // Q --.-
{ 3, 0b010 }, // R .-.
{ 3, 0b000 }, // S ...
{ 1, 0b1 }, // T -
{ 3, 0b001 }, // U ..-
{ 4, 0b0001 }, // V ...-
{ 3, 0b011 }, // W .--
{ 4, 0b1001 }, // X -..-
{ 4, 0b1011 }, // Y -.--
{ 4, 0b1100 }, // Z --..
// 0-9
{ 5, 0b11111 }, // 0 -----
{ 5, 0b01111 }, // 1 .----
{ 5, 0b00111 }, // 2 ..---
{ 5, 0b00011 }, // 3 ...--
{ 5, 0b00001 }, // 4 ....-
{ 5, 0b00000 }, // 5 .....
{ 5, 0b10000 }, // 6 -....
{ 5, 0b11000 }, // 7 --...
{ 5, 0b11100 }, // 8 ---..
{ 5, 0b11110 }, // 9 ----.
};
void CMT2119AInit(void) {
pinMode(TWICLK, OUTPUT);
digitalWrite(TWICLK, HIGH);
pinMode(TWIDAT, OUTPUT);
digitalWrite(TWIDAT, LOW);
// Enable internal pullups for TWI communication
digitalWrite(TWICLK, HIGH); // Pullup already set via OUTPUT HIGH
}
void CMT2119ASetDefault(void) {
for (int i = 0; i < 21; i++) {
TWI_RAM1(i, CMT2119ook[i]);
}
CMT2119ASetFrequency(0);
CMT2119AUpdate();
}
double CMT2119AGetPfd(void) {
double pfd = refOsc / 131072.0;
return pfd;
}
void CMT2119ASetFrequency(double direct) {
bool freqOK = false;
double freq;
double pfd;
uint8_t prescale15;
uint8_t prescale2;
double n;
char resp;
pfd = CMT2119AGetPfd();
freqOK = true;
if (freq <= 320.0) {
prescale15 = 1;
prescale2 = 1;
} else if (freq <= 480.0) {
prescale15 = 0;
prescale2 = 1;
} else if (freq <= 640.0) {
prescale15 = 1;
prescale2 = 0;
} else {
prescale15 = 0;
prescale2 = 0;
}
if (prescale15) TWI_RAM1(6, 0x0001);
if (prescale2) TWI_RAM1(1, 0x5400);
if (prescale15) freq = freq * 1.5;
if (prescale2) freq = freq * 2.0;
//frequency
uint32_t pll = round((freq / pfd) / 2) * 2; //round to nearest even number
TWI_RAM1(7, pll & 0xfffe); //lsb must always be zero.
uint16_t pllh = (pll >> 8) & 0xFF00;
TWI_RAM1(8, pllh);
TWI_RAM1(9, 0);
}
void TWI_reset(void) {
digitalWrite(TWIDAT, LOW);
digitalWrite(TWICLK, HIGH);
delayMicroseconds(1);
for (uint8_t i = 0; i < 32; ++i) {
digitalWrite(TWICLK, LOW);
delayMicroseconds(1);
digitalWrite(TWICLK, HIGH);
delayMicroseconds(1);
}
TWI_WRREG(0x0d, 0x00);
}
void TWI_Write(uint8_t x) {
digitalWrite(TWICLK, HIGH);
digitalWrite(TWIDAT, LOW);
for (uint8_t i = 0; i < 8; ++i) {
digitalWrite(TWICLK, HIGH);
if (x & 0x80) digitalWrite(TWIDAT, HIGH);
else digitalWrite(TWIDAT, LOW);
delayMicroseconds(1);
digitalWrite(TWICLK, LOW);
delayMicroseconds(1);
x <<= 1;
}
digitalWrite(TWICLK, HIGH);
digitalWrite(TWIDAT, LOW);
}
void TWI_WRREG(uint8_t addr, uint8_t data) {
TWI_Write(0x80 | (addr & 0x3f));
TWI_Write(data);
}
void TWI_RAM1(uint8_t addr, uint16_t data) {
TWI_WRREG(0x18, addr);
TWI_WRREG(0x19, data & 0xff);
TWI_WRREG(0x1A, data >> 8);
TWI_WRREG(0x25, 0x01);
}
void CMT2119AUpdate(void) {
TWI_reset(); //step 1
TWI_WRREG(0x3d, 0x01); //step 2 send SOFT_RST
delay(2);
//some proprietary command preamble from the datasheet
TWI_WRREG(0x02, 0x78); //Open LDO & Osc step 3
TWI_WRREG(0x2F, 0x80); //vActiveRegsister step 4
TWI_WRREG(0x35, 0xCA);
TWI_WRREG(0x36, 0xEB);
TWI_WRREG(0x37, 0x37);
TWI_WRREG(0x38, 0x82);
TWI_WRREG(0x12, 0x10); //vEnableRegMode step 5
TWI_WRREG(0x12, 0x00);
TWI_WRREG(0x24, 0x07);
TWI_WRREG(0x1D, 0x20);
//program the default RAM config by RFPDK generated setup
//TWI_RAM(chanData[channel].reg,21); //step 6
for (int i = 0; i < 21; i++) {
TWI_RAM1(i, CMT2119ook[i]);
}
TWI_WRREG(0x0D, 0x02); //step 7 send the TWI_OFF command. Control reverts to simple DAT signals
digitalWrite(TWIDAT, HIGH); //output on
delay(2);
}
void tx_on(void) {
digitalWrite(TWIDAT, HIGH);
}
void tx_off(void) {
digitalWrite(TWIDAT, LOW);
}
void send_dot(void) {
tx_on();
delay(DOT_MS);
tx_off();
delay(SYMBOL_SPACE_MS);
}
void send_dash(void) {
tx_on();
delay(DASH_MS);
tx_off();
delay(SYMBOL_SPACE_MS);
}
void send_char(char c) {
uint8_t idx;
// Convert to uppercase
if (c >= 'a' && c <= 'z') {
c = c - 'a' + 'A';
}
// Get index
if (c >= 'A' && c <= 'Z') {
idx = c - 'A';
} else if (c >= '0' && c <= '9') {
idx = c - '0' + 26;
} else if (c == ' ') {
delay(WORD_SPACE_MS - CHAR_SPACE_MS);
return;
} else {
return; // Unknown character
}
morse_t m = morse_table[idx];
// Send morse pattern
for (int8_t i = m.len - 1; i >= 0; i--) {
if (m.pattern & (1 << i)) {
send_dash();
} else {
send_dot();
}
}
delay(CHAR_SPACE_MS - SYMBOL_SPACE_MS);
}
void send_message(const char* msg) {
while (*msg) {
send_char(*msg);
msg++;
}
}
void TWI_EEPROM_SETUP(void) {
TWI_WRREG(0x02, 0x3B);
TWI_WRREG(0x2F, 0x80);
TWI_WRREG(0x3F, 0x01);
TWI_WRREG(0x16, 0x31);
TWI_WRREG(0x35, 0xCA);
TWI_WRREG(0x36, 0xEB);
TWI_WRREG(0x37, 0x37);
TWI_WRREG(0x38, 0x82);
}
void TWI_EEPROM_END(void) {
TWI_WRREG(0x16, 0x30);
TWI_WRREG(0x3F, 0x00);
TWI_WRREG(0x0C, 0x27);
TWI_WRREG(0x2F, 0x00);
TWI_WRREG(0x02, 0x7F);
TWI_WRREG(0x0C, 0x00);
TWI_WRREG(0x3D, 0x01); //SOFT_RESET
}
uint8_t TWI_RDREG(uint8_t addr) {
TWI_Write(0xc0 | (addr & 0x3f));
return TWI_Read();
}
uint8_t TWI_Read(void) {
uint8_t r = 0;
pinMode(TWIDAT, INPUT_PULLUP);
digitalWrite(TWICLK, HIGH);
for (uint8_t i = 0; i < 8; ++i) {
digitalWrite(TWICLK, HIGH);
delayMicroseconds(1);
r <<= 1;
if (digitalRead(TWIDAT)) r |= 1;
digitalWrite(TWICLK, LOW);
delayMicroseconds(1);
}
digitalWrite(TWICLK, HIGH);
pinMode(TWIDAT, OUTPUT);
digitalWrite(TWIDAT, LOW);
return r;
}
void TWI_EEPROM_ERASE(uint8_t add) {
uint8_t resp;
TWI_WRREG(0x17, add); //Set the EEPROM Address
TWI_WRREG(0x16, 0x39); //start the erase
do //wait till the erase has completed
{
delay(1);
resp = TWI_RDREG(0x1F);
} while ((resp & 0x08) == 0);
TWI_WRREG(0x16, 0x31); //end the erase
}
void TWI_EEPROM_WRITE(uint8_t add, uint16_t dat) {
uint8_t resp;
TWI_WRREG(0x17, add); //Set the EEPROM Address
TWI_WRREG(0x19, dat & 0xFF); //Set the EEPROM Low Byte
TWI_WRREG(0x1A, dat >> 8); //Set the EEPROM High Byte
TWI_WRREG(0x16, 0x35); //start the write
do //wait till the erase has completed
{
delay(1);
resp = TWI_RDREG(0x1F);
} while ((resp & 0x08) == 0);
TWI_WRREG(0x16, 0x31); //end the write
}
uint16_t TWI_EEPROM_READ(uint8_t add) {
uint8_t resp;
uint16_t val;
TWI_WRREG(0x17, add); //Set the EEPROM Address
TWI_WRREG(0x16, 0x33); //start the read
do //wait till the erase has completed
{
delay(1);
resp = TWI_RDREG(0x1F);
} while ((resp & 0x08) == 0);
val = (TWI_RDREG(0x1C) << 8) + TWI_RDREG(0x1B);
TWI_WRREG(0x16, 0x31); //end the read
return val;
}
void CMT2119A_EEPROM_BURN(void) {
Serial.println("Burn and Verify Start");
TWI_reset();
TWI_reset();
TWI_EEPROM_SETUP();
for (int r = 0; r < 0x15; r++) //erase and write EEPROM values 0x00 - 0x14
{
TWI_EEPROM_ERASE(r);
if (r < 21) {
TWI_EEPROM_WRITE(r, CMT2119ook[r]);
} else {
TWI_EEPROM_WRITE(r, 0);
}
}
for (int r = 0; r < 0x15; r++) //verify the EEPROM values 0x00 - 0x14
{
uint16_t expected = (r < 21) ? CMT2119ook[r] : 0;
uint16_t val = TWI_EEPROM_READ(r);
if (val != expected) {
Serial.print("Verify Error at address = ");
Serial.print(r, HEX);
Serial.print(" Expected = ");
Serial.print(expected, HEX);
Serial.print(" Value = ");
Serial.println(val, HEX);
}
}
TWI_EEPROM_END();
Serial.println("Burn and Verify Complete");
Serial.println("Resetting from CMT2119A EEPROM");
CMT2119A_RESET();
digitalWrite(TWIDAT, HIGH);
}
void CMT2119A_RESET(void) {
TWI_reset(); //step 1
TWI_WRREG(0x3d, 0x01); //step 2 send SOFT_RST
delay(2);
TWI_WRREG(0x0D, 0x02); //step 7 send the TWI_OFF command. Control reverts to simple DAT signals
}
void setup() {
Serial.begin(115200);
while (!Serial)
;
Serial.println("CMT2119A OOK CW Transmitter");
CMT2119AInit();
CMT2119AUpdate();
CMT2119A_EEPROM_BURN();
CMT2119ASetDefault();
Serial.println("Initialized. Starting transmission...");
}
void loop() {
send_message("CQ CQ CQ DE A TEST MESSAGE");
tx_off();
delay(2000); // Pause between transmissions
}
References: