I am using Ionos because someone recommend me to make VM (Virtual Machine) on that service using VPS (Virtual Private Server). While it looks simple, there should be followed many steps very carefully. First, we need to chose the subscription. If you need it for port forwarding for Helium mining, then $2 US monthly is sufficient.
Open your account on next page and chose “VPS S”; 512 MB of RAM, 1 CPU, 10 GB of disk space (pay attention to chose Data center closest to you):
https://www.ionos.com/servers/vps#packages
Chose Ubuntu 20.04 or whatever is newest and shown below. You may at any time upgrade to VPS S+ with more RAM and disk space if needed.
Please chose Data center near you. I did manage to chose wrong one (USA, which is far) and everything is bit slower. For example, I am in Croatia and I should to chose Germany.
Since I do not want to host any data ‘hungry’ Virtual Machine, I did not chose any backup. It is up to you. I need it for port forwarding only. After “Continue” proceed to payment section. I chose PayPal because of extra layer of protection.
Then go to https://my.ionos.com/ and click to “Servers and Cloud”
If I remember correctly, having just one VM (Virtual Machine) brings you directly to the Server settings, but I have two VMs, so it offers me to chose which one:
New one is left (VPS S). The old one is just upgraded (VPS S+) for another reason.
Before continuing with the rest, we need to set Ionos VM firewall. I always forgetting that part and then wonder why my VPN does not work. 😀
On left menu, select Network, then Firewall Policies, then click on Linux (all three marked red).
We need to add few more rules:
ICMP is for Ping test. Port 51820 is for the VPN tunnel, and port 44158 for Helium miners. When you connect your Wireguard client, negotiation and traffic goes over the port 51820 on the Ionos VM and their routers. When your miner need port 44158, this VM firewall actually permitting on their routers inbound traffic, then send it over the VPN tunnel to your Mango router where is your miner connected. So, instead port forward on your router connected to your ISP, it is port forwarded on your Ionos VM and their routers at their end, and our Mango router provide port forwarding at our end to its LAN port (and WiFi if enabled).
Now, chose “Servers”, click on your new VPS and scroll down.
Please note that I redacted my IP for security reasons. On this page we need two things: that IP and the password. By using Notepad or other software, copy paste that IP and for our SSH we need to form the command line on command prompt (in Windows it is CMD).
ssh [email protected], where you should replace that four numbers with your IP. It will ask you for password. From your note in Notepad, copy password, and since in CMD you can’t paste it by Ctrl+V, simply right mouse click and it will be there, but invisible. Just press enter after that.
Then you will get this:
First thing we need to do is few quick steps:
apt update
apt upgrade
apt install iptables
First two commands are for update and upgrade server for security updates. Third one we need for our port forwarding.
Next thing is check for our interface. Some servers has ‘eth0’, other ‘ens192’ for first interface. Then we type command:
ifconfig
It may complain that package is not installed, just install it (re-type offered command or copy paste command and press enter).
apt install net-tools
Then repeat ifconfig to get this:
Marked red is ‘ens192’. So, now we know what interface should be for our port forwarding purposes.
Now we are ready to install Wireguard on the Virtual Machine:
It will ask you for the ‘listening port’, type 51820 and press enter. Next is the name of the ‘VPN tunnel’, you may chose simple name with no space. In my case I chose Vera, so that will form Vera.conf in your root directory. Type commald
ls
That will show the content of the directory. Two files, of which first is important. By using text editor ‘nano’ open this configuration file
nano Name.conf
Instead “Name” use name that you chose.
On Windows computer, you may use Ctrl+C to copy content, but first select it by using mouse left click and drag over whole text. So first select, then Ctrl+C.:
The content we just copied paste onto your Notepad and save as Name.conf (replace Name with your chosen one). Please select all files, else it will be saved as Name.conf.txt. If you missed that point, then open File explorer and rename this file to proper Name.conf. We need this file during setting Wireguard client. It can be found HERE.
After installing WIreguard client, we need to add our first ‘VPN tunnel’.
Now you may see why simple name is preferred. If everything is okay, and after setting, you may test your first VPN by clicking on ‘Activate’. To see your new IP address (port forward not yet done), go to https://www.whatismyip.com/ and observe new IP address of your VPN.
Part 1 is done. For port forwarding, we need to do more things. Note that only one VPN tunnel is possible to your computer. After setting your Mango router, you can activate VPN tunnel from there, but not both: on your Windows (or whatever) machine and on the router.
Go back to your CMD console providing that you are still connected by ssh to the VM. type:
cd /etc
ls
to see the list
nano sysctl.conf
to open that configuration file and by using arrow key on your keyboard, go to the bottom, then copy paste next row:
net.ipv4.ip_forward=1
Which will enable port forwarding. Press Ctrl+O to save, when prompted, press enter. Exit by using Ctrl+X.
sysctl -p
to make it permanent. It will show you that net.Ipv4…. message. Just one more step to set port forwarding rules. Go to:
cd wireguard
ls
nano wg0.conf
It will show our Wireguard server setting. We should to move cursor with arrow keys on our keyboard between “Listen port…” and “# BEGIN_PEER..” and move it away by using enter. If you remember that we on Ionos using ‘ens192’ interface, then next script should be paste/copy (“Copy to clipboard, upper right corner” from this page or from your Notepad, and right click on the cursor on the nano text editor) next:
To exit, press ‘Q’. You may also check status with:
wg
To see the status before VPN tunnel is established on our Mango router.
—
Now, unpack your Mango router (Gl-Inet) and connect WAN port to your current router/WiFi modem, LAN port to your Helium miner, and we will establish connection to the Mango router over it’s own WiFi. On your laptop or PC (with WiFi dongle) select the name of the WiFi (something like “GL MT300N-V2-c”) and type default password “goodlife”. It will prompt you to change this password and repeat. Write down somewhere this password, you will need it later.
To access this router and change settings, go to your favorite browser and type local IP of the router: 192.168.8.1 . Be sure that you are on the same network (for example on the WiFi connection on this router. It is good practice to go to ‘WIRELESS’ and change password there too.
Now, we should to go to ‘CLIENTS’ and copy paste MAC addresses of our Helium miner and or laptop/PC. We need that to set VPN Policies and binding IP addresses for our Helium miner, so that every time it get the same IP no matter how we connect it (over additional switcher, directly…).
Now, you should to make decision whether you want only Helium miner to be on the VPN, both; miner and your PC/laptop. If just Helium miner, then you need to add only that MAC address to the VPN Policies. This is because if only Helium miner is on the VPN, you may continue to use your ‘true’ IP from your ISP (Internet Service Provider). I rather chose both, because of testing purposes. So, VPN polices should look like this:
So, you should to ‘Enable VPN policy’, disable next two; ‘Use VPN for quest network’ and ‘Use VPN for processes on the router’. Under ‘Please chose policy’ select ‘MAC address’, and under ‘Pease chose rules’ select ‘Only allow the following use VPN’. Now, find your MAC address of your Helium miner and copy paste it on the field and press ‘add’. If you want your PC/laptop, add that too.
Firewall will be set last, because we need Wireguard client tunnel to be established, else we will be unable to set it properly.
Since we already have script for the Wireguard tunnel, we just need to copy/paste all three keys from Wireguard script to our Mango router. First select ‘Wireguard Client’ under ‘VPN’, then add new…
There is ‘+ Setup Wireguard manually’, click on that and select third tab from the left saying ‘Manual input’.
We need to fill all fields, which is now easy since we have already our Wireguard client installed. Run that client, click on ‘Edit’ to get this:
By using select with mouse, Ctrl+C then Ctrl+V onto Mango router fields, fill everything. Since all names are the same, there is almost no room for the confusion where goes what. Only MTU is need to be manually set to 1420. When we are done, click on ‘Apply’.
And now we can run our VPN and set firewall rules. If everything is okay, then we should se something like this:
Go to ‘Firewall’ and type name of the port forward. I am choosing simple “Helium” name. Then ‘TCP/UDP’, then select ‘wireguard’ (if tunnel is not established, this option will not show there. Then type port 44158, then chose ‘lan’, then your internal IP (that is IP of the Helium miner), again port 44158, and leave enabled. Click to ‘add’.
If everything is okay, then we should to go to https://portchecker.co/
to check whether our port 44158 is open. Pay attention to the IP, if you are connected outside VPN tunnel, it will autofill with different address. No problem, from our Notepad copy/paste proper IP (one that our VM on the VPS we got). Then you should see green ‘OPEN’.
If you want, you may go back to our ssh console and type
wg
To check whether it is properly connected. You may see your ‘endpoint’ IP from where is tunnel connected. That is all.
You may use Blender 2.80 as well, or any other versions (2.79, 2.82, 2.83 or other branches). I just found convenient to use Blender 2.81 which is relatively stable and has pretty good options.
Example in this tutorial. Left is single frame before inserting 3D object, and right is frame with 3D object, all reflections and added shadow:
Also, the final video is here:
In this tutorial, we will track front face of the building, set ‘floor/wall’ and add some object to combine video with 3D scene in Blender. There are few versions – complex one with adding shadows, reflections and everything else. But I will also include simplified version.
I will start with essential tracking, camera solving and making simple 3D scene – which will be then combined with video example (included in this page) by using compositing nodes. So, first part of the video will be as simplest as possible. You may watch just that part of the video, or continue as things become more complicated. Or, you may back at any time and watch the rest of the video with more and more complex 3D scene, and more complex compositing.
If you want, you may use your own video and image examples, but I am trying to offer you the best quality material as possible. That means, low amount of motion blur, relative low amount of camera shaking (working on it), sharp photos and videos and so on. Also, I will try to include all other material and guides for easier following of those tutorials. If you spot something missing, feel free to comment on this post, and/or video tutorial on YouTube.
HDRI image is not needed if we do only simple scene, but if we use reflective 3D objects, then it is good practice to use ‘ambient’ or ‘world’ node in shading part of the Blender.
Media files for test tutorials that can be seen on my new YouTube channel. This one is without sound. :
Click on the link, then after video starts, right mouse click and chose “Save video as…” . Save video to your working folder. You may use this video for your videos, YouTube tutorials or whatever you want.
HDRI photo for this scene in Blender:
Click on this photo and right click and chose “Save image as…” . Save image to your working folder. This panoramic view is free to use for your tutorials, or whatever you want. It is Creative Commons licence – reuse allowed.
Compositing nodes
Node tree for this example – complex tree (it may be reduced). :
The same complex node tree, but this one is inverted, so ‘printing friendly’. You may download this image, print it and use it as a reference for constructing your node tree. :
The same rules apply; click on the image, then right mouse click and chose “Save image as…” and save it in your working folder. Whatever is easier to you – watching previous image, or print inverted version (less tint/toner consumption).
Even more complex situation, where we back and distort image again to fit the frame:
Simplified version that ignore (removed) ‘undistort’, ‘scale’, and two 3D sub-scenes (‘foreground’ and ‘background’):
And, the same node tree, inverted as a ‘printer friendly version’:
And the simplest version with noise reduction (denoise) removed. That is ‘bare minimum’ for VFX work to combine ‘real world’ and ‘3D world’. :
And again inverted version for printing.:
Shading nodes
We are not yet done with nodes. Above are nodes for compositing, but now follows node for shading. First one is HDRI image that gives us source for reflection of the metal ball (3D world).:
Note cropped part – that is our video file combined with ball from 3D world, the rest is HDRI image. Middle left on the picture says “World”, that is HDRI image that can give us illumination for 3D scene and source of image for reflection, in this example for metallic ball inserted in front of the building.
Inverted version for printing:
While HDRI image gives us reflection of metal ball outside, building itself may be grey if next shading node tree is not used (no relatively accurate reflection of the building itself, outlined with orange outline):
The same shading node tree, this time inverted:
Shading node tree for sphere and window close to sphere. Both are metallic and with low roughness (very reflective). I will not post separate shading node tree for the sphere and for the window – they differ only in ‘base color’ – window is darker so that reflection matches reflections from other windows. Metallic ball is highly reflective, while glass is slightly reflective. Base color if darker gives us exactly that small amount of reflectivity (outlined orange):
Got this beautiful Flytec car from Banggood. It is not proportional, but for such low price (price fluctuate, at first it was $27, then go down to $25, then $20, and now is more like $25 US again). And, if you want some discount, please use Coupon Code: RCsuek
By choosing my affiliate link, you helping me make more reviews:
Range of remote controller is about 25 meters. Tried to change antenna on the transmitter, but seems that this thing work on WiFi or BlueTooth, so once out of range, you should go closer and it will bind again.
NiCd battery
Yeah, I am not happy with this battery. There is room however to use some Li-Po battery, say 1S (3.7V). Someone told me that 2S (7.4V) is too much and that car goes too fast. I bet that motors and everything else wear faster at higher voltage. So, keep it below 5V. Charging time is cca. 3 hours (charger is included, and it is USB charger), that is another bad – I wish to have more battery pack, so that I can change on field quickly.
Not proportional
I know, that is bad thing. But with some patience, it is possible to learn to drive it as if it has proportional controls for wheels and throttle. Usually I am using this car to play with my cats. 😀 They are not scared anymore, since many times I have toys that produce weird sounds as is quadcopters for indoor flight. They are just curious.
Plastic casing
Just roof and cabin is made of aluminum, everything else is plastic. But, I am surprised how though is that plastic. So, no worry there.
Rubber wheels
Really good one – little bit on soft side, but this car is not too heavy, and ‘dancing’ on rocks like it should dance (please take look at my video above).
Blinking LED
Inside is red/blue LED that changes color. I wish that it has front and back lights, but for cca. $20-$25, I can’t complain.
Sometimes device(s) has no standard serial or parallel protocols as are I2C, SPI and others. Instead, just series of bits or bytes and separate clock. Excellent example is DDS chip AD9850 – we may chose or serial or parallel interfacing. On serial interfacing we using ‘bit banging’ – we put one bit on GPIO port, then on another GPIO port one clock, then next bit on port… until all bites are transferred. Then we send frequency and phase update on third wire, brief pulse 1 then 0. If we want parallel (8 times faster) interfacing, we put whole byte on 8 pins, in our example GPIO A0 to A7, then on another wire we ‘fire’ clock pulse, and after transferring all data, also ‘firing’ update frequency and phase. Before proceed to our program in Keil, we should to consult our datasheet about AD9850:
FM radio or Frequency Wobbler
In the codes below, there are two options: FM radio (chose for example 1/3 of the frequency, say 33 MHz, then ‘catch’ third harmonic on 99 MHz, and use comparator for getting almost square wave, so that odd harmonics are stronger), or “Frequency Wobbler”, excellent tool for RLC circuit characterization (testing band pass, low pass, notch and other filters as well as resonant circuits) from 0 to 62.5 MHz with AD9850 – or up to 90 MHz with AD9851, which is really hard to find. You may change code so that sweep is only upward, and then program one pin before sweep to output short pulse for oscilloscope external trigger. Or leave as is and listen on SDR Software Defined Radio response from RLC circuits.
I made both options available in the same program, just pay attention to GPIOA registers – there are difference in the case of serial and in the case of parallel interfacing. Also, for serial or parallel, there are functions “dds_update_freqS” and “dds_update_freqP”, probably you correctly guessed, ‘P’ is for parallel, ‘S’ is for serial. Now, copy paste this code as usually onto your Keil editor:
/**********************************************************************
Short but sweet sweep program. It goes from wanted low and high
frequency with desired steps. In this example with parallel mode,
it goes 8 times faster than in serial mode.
======================Pin map:=======================
STM32F103C8 AD9850 ST-LINK V2
GND---------------GND-----------GND
VCC----------------------------+3.3V
..................VCC----------+5V
B1----------------WCLK
B10---------------FQUP
B11---------------RESET
A0----------------D0
A1----------------D1
A2----------------D2
A3----------------D3
A4----------------D4
A5----------------D5
A6----------------D6
A7----------------D7
======================================================
This example is free to use, share modify... do what you want. :D
*******************************************************************/
#include "stm32f10x.h"
uint8_t phase = 0;
int analog = 0;
//in the case of sweep function:
int sweep_begin = 0;
int sweep_end = 0;
int sweep_step = 0;
//in the case of FM radio:
float freq= 26e6; // "Intended" frequency (33e6= 33 000 000 Hz, or 33 MHz), but:
float offset= 49.5e3; // Since ADC is directly connected to analog input, it has some non-zero voltage.
// This voltage should be ideally VCC/2, or 3.3V/2= 1.65V, but it is not always true.
// So, offset should be subtracted from intended frequency so that final frequency is where we want.
// Set this number zero, then look at your receiver (RTL SDR, for example), how much it goes up,
// then set this offset. This number is subtracted inside update (in IRQ handler function).
// In my case, it is 49.0 KHz above, so positive value will be calculated inside IRQ thing (freq-offset)
void delay(long cycles)
{
while(cycles >0)
cycles--; // Some stupid delay, it is not in milliseconds or microseconds, but rather in some 'wasted clock cycles'
}
void ADC_enable(void)
{
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;// ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz / 6 = 12MHz).
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_AFIOEN; //enabling ADC clock, interrupt enable,
//while clock for port A and B is enabled down below
ADC1->CR1 |=ADC_CR1_EOCIE; //ADC interrupt enabled
NVIC_EnableIRQ(ADC1_2_IRQn); //interrupt enabled
ADC1->SMPR2 |= ADC_SMPR2_SMP8_0|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
ADC1->SQR3 |= ADC_SQR3_SQ1_3; //for B0 in sequence 1, channel 8, it is 0b1000 = 8 (IN8)
//ADC1->SQR3 |=8; // alternative way of setting the same thing as above
ADC1->CR2 &= ~ADC_CR2_ALIGN; //data is right aligned (0bxxxx111111111111)
ADC1->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; //ADC converter is on
delay(1000); //alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...
ADC1->CR2 |= ADC_CR2_CAL;
delay(1000); //it is better to leave some time, just few clock cycles...
ADC1->CR2 |= ADC_CR2_ADON; //not sure it requires to call it again?
delay(1000); //After first ADON, ADC is just set, then second time ADC is actually enabled
}
void gpio_A_and_B_port_enable(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; //ports A & B clock enabled
/* For parallel connection: */
//GPIOA->CRL = 0x33333333; //for parallel - pins A0 to A7 are used, 50 MHz, push-pull
/* For serial connection: */
GPIOA->CRL = 0x34444444; //for serial, only pin A7 is used, other pins MUST be floating
GPIOB->CRL = 0x44444430; //Pin B0 is analog input, pin B1 is digital output, 50 MHz, push-pull
GPIOB->CRH = 0x44443344; //Pins B10 and B11 are digital putput, 50 MHz, push-pull
}
void reset_d(void)
{
GPIOB->BSRR = GPIO_BSRR_BS11; // Reset, pin B11
GPIOB->BSRR = GPIO_BSRR_BR11;
}
void clock_d(void)
{
GPIOB->BSRR = GPIO_BSRR_BS1; // Clock, pin B1
GPIOB->BSRR = GPIO_BSRR_BR1;
}
void update_d(void)
{
GPIOB->BSRR = GPIO_BSRR_BS10; // Update, pin B10
GPIOB->BSRR = GPIO_BSRR_BR10;
}
void dds_reset(void)
{
// Set everything low first
GPIOB->BRR = GPIO_BRR_BR1|GPIO_BRR_BR10|GPIO_BRR_BR11; //instead BSRR, we use BRR, Bit Reset Retister
reset_d(); // Pulse reset
clock_d(); // Pulse clock
update_d(); // Pulse load
}
void dds_writeParallel(uint8_t byte)
{
GPIOA->ODR = byte;
clock_d();
}
void dds_writeSerial(uint8_t byte)
{
uint8_t i;
uint8_t bit;
for(i = 0; i < 8; i++)
{
bit = ((byte >> i) & 1);
if(bit == 1)
GPIOA->BSRR = GPIO_BSRR_BS7; // Data bit set on pin A7
else
GPIOA->BSRR = GPIO_BSRR_BR7; //data bit reset (A7)
clock_d();
}
}
/* Parallel updating frequency - different than serial */
void dds_update_freqP(float freq)
{
uint32_t tuning_word = (freq * 34.36426); // resolution is 0.0291 Hz, so 1/0.0291 = 34.36426 steps per 1 Hz
/* Another chip, AD9851 has almost the same properties. Just different resolution + ability to use low frequency
oscillator by enabling internal multiplier x6 on first word W0, which should be: 0b00000010 */
dds_writeParallel(((uint8_t)0b00000000) & 0xFF); // 0b00000100 = W0* - power off - should be phase + pwr_down + control bits
//0bxxxxx, five upper bits for phase, 180/90/45/22.5/11.25 degrees, then one bit for power down (if set), and two zeroes (reserved)
dds_writeParallel((tuning_word >> 24) & 0xFF);
dds_writeParallel((tuning_word >> 16) & 0xFF);
dds_writeParallel((tuning_word >> 8) & 0xFF);
dds_writeParallel(tuning_word & 0xFF);
update_d();// Load (f_upd)
// *-> in parallel mode, it is possible to send only W0 for changing phase and power up/down.
}
/* Serial updating frequency - different than in parallel */
void dds_update_freqS(float freq)
{
// Updates DDS output frequency. Supply frequency in Hz.
uint32_t tuning_word = (freq * 34.36426); // resolution is 0.0291 Hz, so 1/0.0291 = 34.36426 steps per 1 Hz
dds_writeSerial(tuning_word & 0xFF);
dds_writeSerial((tuning_word >> 8) & 0xFF);
dds_writeSerial((tuning_word >> 16) & 0xFF);
dds_writeSerial((tuning_word >> 24) & 0xFF);
dds_writeSerial(0b00000000); // 0b00000100 = power off
update_d(); // this function finally send signal to AD9850 to update frequency and phase
}
void sweep(int sweep_begin, int sweep_end, int sweep_step)
{
for (int i=sweep_begin;i<sweep_end;i+=sweep_step)
{
dds_update_freqS(i);
//delay(50); //if you want slow up upward sweeping
}
//now going back. If you want only one way (up), then remove or comment (/**/) for loop below for only down,
//do the same with for loop above.
for (int i=sweep_end;i>sweep_begin;i-=sweep_step)
{
dds_update_freqS(i);
//delay(50); //if you want to slow down sweeping downward
}
}
void ADC1_2_IRQHandler(void)
{
if (ADC1->SR & ADC_SR_EOC)
{
analog=ADC1->DR;
dds_update_freqS((freq-offset)+(analog*26)); //FM modulation. No multiplier for NFM, but for WFM it is an*26 or so...
}
}
int main()
{
gpio_A_and_B_port_enable();
dds_reset();
ADC_enable();
while (1)
{
//sweep(25e6,26e6,1e3); //frequency begin, frequency end, step size
}
}
Depends of which board we have. If chip is STM32f103c8, then there is no DAC on it, but we can mimic DAC by using PWM. If board has stm32f103vet6 on the other hand, it has DAC and we can use both channels as stereo. Please watch video first, then it will be clear why we need move SPI pins with AFIO_MAPR function to another place – both shares the same pins.
#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"
#include "adc.h"
#include "dac.h"
#include "spi.h"
#include "sample.h"
#include "pwm_as_dac.h"
int main(void)
{
usart_1_enable();
timer2enablePWM();
spiEnable();
adcEnable();
dacEnable();
//setModule(433.120,0); // (frequency in MHz, power in steps from 0 to 7, or 1.26 mW to 100 mW)
while(1)
{
//adc();
for (int i=0;i<raw;i++)
{
PWMdac(rawData[i]/4,rawData[i]/4); //since our PWM DAC imitation has only 8 bit resolution, we need to divide 4096 / 4
dac(rawData[i]*10-2048,rawData[i]*10-2048); //8 bits to 12 bits resolution, but it may be needed to amplify by multiplying *10 and offset -2400
delay(330);
}
dac(left,right); //normal DAC, if inputs are 12 bits, the output is also 12 bit
delay(50000000); //long delay between two plays
}
}
#include "stm32f10x.h"
#include "delayUs.h"
#include "adc.h"
#include "dac.h"
int left=0;
int right=0;
void adcEnable(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_ADC1EN |RCC_APB2ENR_ADC2EN | RCC_APB2ENR_AFIOEN; //enabling ADC clock, interrupt enable,
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;// ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz / 6 = 12MHz). works with div6 too
//while clock for port A and B is enabled down below
// pin B0 is analog input, no need special GPIO setting
// pin B1 is analog input, no need special GPIO setting
ADC1->CR1 |=ADC_CR1_EOCIE; //ADC interrupt enabled
ADC2->CR1 |=ADC_CR1_EOCIE; //ADC interrupt enabled
//NVIC_EnableIRQ(ADC1_2_IRQn); //interrupt enabled
ADC1->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
ADC2->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
ADC1->SQR3 |=8; //L B0
ADC2->SQR3 |=9; //R B1
ADC1->CR2 &= ~ADC_CR2_ALIGN; //data is right aligned (0bxxxx111111111111)
ADC2->CR2 &= ~ADC_CR2_ALIGN;
ADC1->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; //ADC converter is on
ADC2->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT;
delay(1000); //alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...
ADC1->CR2 |= ADC_CR2_CAL;
ADC2->CR2 |= ADC_CR2_CAL;
delay(1000); //it is better to leave some time, just few clock cycles...
ADC1->CR2 |= ADC_CR2_ADON; //not sure it requires to call it again?
ADC2->CR2 |= ADC_CR2_ADON;
delay(1000); //After first ADON, ADC is just set, then second time ADC is actually enabled
}
/* For enabling IRQ, uncomment three lines above */
void ADC1_2_IRQHandler(void)
{
left=ADC1->DR;
right=ADC2->DR;
}
/* Manual call of the ADC conversion channels */
void adc(void)
{
ADC1->CR2 |=ADC_CR2_SWSTART;
ADC2->CR2 |=ADC_CR2_SWSTART;
left=ADC1->DR;
right=ADC2->DR;
}
#ifndef adc_h
#define adc_h
extern int left;
extern int right;
void adcEnable(void);
void adc(void);
#endif
#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"
void dacEnable(void)
{
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;// trying to divide clock by 2 for APB1, for max freq of 36 MHz
RCC->APB1ENR |= RCC_APB1ENR_DACEN; //DAC interface clock enable
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
DAC->CR |= DAC_CR_BOFF1|DAC_CR_BOFF2; //DAC buffer
//DAC->CR |= DAC_CR_WAVE2_1|DAC_CR_WAVE2_0|DAC_CR_WAVE1_1|DAC_CR_WAVE1_0;
//DAC->CR |= DAC_CR_MAMP1_3|DAC_CR_MAMP2_1|DAC_CR_MAMP1_1|DAC_CR_MAMP1_0; //maximum aplitude?
//DAC->CR |= DAC_CR_MAMP2_3|DAC_CR_MAMP2_2|DAC_CR_MAMP2_1|DAC_CR_MAMP2_0; //maximum aplitude?
DAC->CR |= DAC_CR_TSEL1_0|DAC_CR_TSEL1_1|DAC_CR_TSEL1_2; //software trigger 0b111
DAC->CR |= DAC_CR_TSEL2_0|DAC_CR_TSEL2_1|DAC_CR_TSEL2_2; //software trigger 0b111
DAC->CR |= DAC_CR_TEN1|DAC_CR_EN1; //software trigger for DAC1 enable, DAC1 enable
DAC->CR |= DAC_CR_TEN2|DAC_CR_EN2; //software trigger for DAC2 enable, DAC2 enable
}
void dac(int left, int right)
{
DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG1;
DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG2; //not sure which trigger, so both...
DAC->DHR12R1 = left; //currently left channel, pin A4
DAC->DHR12R2 = right; //currently right channel, pin A5
}
/* This software is provided by https://wildlab.org and
Milan Karakas from Croatia. This is free program, but
also "beerware". This means if you want this beta test
phase to grow into something really great, please consider
some donation here:
https://www.paypal.me/milankarakas?locale.x=en_US
It is still in beta test phase, and will be upgraded.
So far it works at 433.120 MHz, and you need to change
the frequency if you wish to something else - in library
'Si4432.c', there is math and you should to calculate
and change values of the register for other frequencies.
Out there exist also Si4432 - 868 MHz module, and this
program works with this one too, but then you MUST change
the frequency, else output TX amplifier may burn.
Later, will ad my own math to do that, but since it is still
in beta testing phase... stay tuned.
*/
#include "stm32f10x.h"
void spiEnable(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN|RCC_APB2ENR_IOPBEN|RCC_APB2ENR_AFIOEN; //already exist, but anyway...
/* Remapping SPI pins */
AFIO->MAPR |=AFIO_MAPR_SWJ_CFG_1; //first we should to enable afio clock (above), then this one "JTAG-DP Disabled and SW-DP Enabled", releasing PA4 and PA5
AFIO->MAPR |=AFIO_MAPR_SWJ_CFG_0|AFIO_MAPR_SPI1_REMAP; //moving SPI1 from PA4/PA5/PA6/PA7 to PA15/PB3/PB4/PB5 (PA15=NSS, PB3=SCK, PB4=MISO, PB5=MOSI
/* GPIO pin A15 (was A4) is NSS ("Not" Slave Sellect), inverted... if low (0), then it is selected */
GPIOA->CRH |= GPIO_CRH_CNF15_1|GPIO_CRH_MODE15_0|GPIO_CRH_MODE15_1;// 50 MHz - NSS (nSEL on Si4432)
GPIOA->CRH &= ~GPIO_CRH_CNF15_0; //alternate function PP, this A4->moved to A15 pin must be high with external resistor 3kOhm to 4.7kOhm
/* GPIO pin B3 (was A5) is SPI clock SCK */
GPIOB->CRL |= GPIO_CRL_CNF3_1|GPIO_CRL_MODE3_0|GPIO_CRL_MODE3_1;// 50 mhz - SCK A5->moved to B3
GPIOB->CRL &= ~GPIO_CRL_CNF3_0; //alternate function PP
/* GPIO pin B4 (was A6) is MISO */
GPIOB->CRL &= ~(GPIO_CRL_MODE4_0|GPIO_CRL_MODE4_1); // INPUT - MISO, A6->moved to B4
GPIOB->CRL |= GPIO_CRL_CNF4_0;
/* GPIO pin B5 (was A7) is MOSI */
GPIOB->CRL |= GPIO_CRL_CNF5_1|GPIO_CRL_MODE5_0|GPIO_CRL_MODE5_1;// 50 MHz - MOSI, A7->moved to B5
GPIOB->CRL &= ~GPIO_CRL_CNF5_0; //alternate function PP
/* Configuring SPI */
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;// SPI enable
SPI1->CR1 |= SPI_CR1_BR_2;//|SPI_CR1_BR_1;//|SPI_CR1_BR_1|SPI_CR1_BR_0; // 72MHz/?
SPI1->CR1 &= ~(SPI_CR1_CPOL|SPI_CR1_CPHA|SPI_CR1_DFF); // modes, 0 for polarity, and 0 for iddle clock=0, dff=0 (8 bit), lsbfirst=0 (MSB first), *
SPI1->CR2 |= SPI_CR2_SSOE; //NSS enabled
SPI1->CR1 |= SPI_CR1_MSTR;// master configuration
}
void spiWrite(int reg, int cmd)
{
SPI1->CR1 |= SPI_CR1_SPE;
SPI1->DR = (0x80|reg); //usually 0x80 does not belongs to SPI, but here it is "command mode", specific for Si4432
while (!(SPI1->SR & SPI_SR_TXE)){};
SPI1->DR = cmd;
while (!(SPI1->SR &SPI_SR_TXE)){};
while(SPI1->SR & SPI_SR_BSY);
while (!(SPI1->SR & SPI_SR_OVR)){}; //if single byte is sent,...
SPI1->CR1 &= ~SPI_CR1_SPE;
}
int spiRead(int reg)
{
SPI1->CR1 |= SPI_CR1_SPE;
SPI1->DR =reg;
while (!(SPI1->SR & SPI_SR_TXE)){};
while(SPI1->SR & SPI_SR_BSY){}; //waiting little bit longer between two sendings
SPI1->DR = 0xFF; //dummy byte
while (!(SPI1->SR & SPI_SR_RXNE)){};
int rd=SPI1->DR; //dummy read? Whole thing does not working properly without
while (!(SPI1->SR & SPI_SR_RXNE)){};
int r=SPI1->DR;
SPI1->CR1 &= ~SPI_CR1_SPE;
return r;
}
#ifndef spi_h
#define spi_h
extern void spiEnable(void);
extern void spiWrite(int reg, int cmd);
extern int spiRead(int reg);
#endif
Sorry, but last file “sample.h” is too long for this page, so it is in separate window (just select all, copy and paste into your sample.h file: sample
When programming any MCU, no matter Arduino IDE, Keil, Atmel studio or other compilers and assemblers, many times we got intro problem not knowing what happening on our data transfer from and to MCU. For example, we want to use I2C protocol for communicating with some sensor, but it does not respond. Many things can be wrong, and without logic analyzer, we just don’t know whether our code is wrong, the sensor is broken, or maybe something else.
Cheap Chinese clone or professional one?
Good question, depend of many factors. Do you use it for your work, or do you program for fun? I am using for my personal projects, and this one that cost less than $50 US.:
It works fine, but there is limit in speed, especially with more than one channel. It is USB 2.0 product and has no analog part as it has original Saleae Logic analyzer. It is too much expensive for my pocket, especially because I am not professional. I just have dream that it will be mine one day, but until then… Anyway, here is video for anyone who are interested and does not know what I am talking about:
Recently I had problem programming STM32 and connecting OLED display. Despite spending hours googling what exact command should be implemented, and reading their documentation, I was not able to get picture on the OLED display. Then got idea – programmed Atmega 328p with Arduino example, then put logic analyzer to I2C bus, and got the codes for initialization of the display.
Another example is when I wanted to know toy grade quadcopters, how transmitter and receiver know where to ‘jump’ by using ‘frequency hopping’. Using SPI protocol and Logic analyzer, I was able to see exactly what is ‘the secret’. Aside many bytes that transmitter sending for moving flying device up/down, left and right, there is one byte that constantly changes even when remote sticks are still – that is information for next channel. So, TX sending information on current channel, then last bit is channel number for next transmission. Whole protocol is made so that if it goes briefly out of range, or missing one packet due to noise, receiver back to original ‘calling’ channel, and in the same time transmitter sending on this channel periodically next possible channel where will be next time. That is so cool to know.
Yes, DSP (Digital Signal Processing) is possible with some speed limitations. For example, if FIR filter (Finite Impulse Response) has too much taps, whole loop process will be slow, and sampling ratio depends strongly of number of those elements. Out there exist specialized MCUs with additional hardware for floating point calculation (FPU), but our STM 32 or whatever MCU you are using, can do DSP.
First, we need to find some math to calculate ‘taps’, you may use your GNU radio companion for that, or some free online calculators as is this one (really simple): http://t-filter.engineerjs.com/
Just set parameter of wanted frequency filtering, and on the right side you have two choices: plain text or C/C++ code. Chose code and copy/paste into my codes in ‘coefficients.h’ file, replacing old ones (or just comment old one with ‘//’. Change “static double filter_taps[FILTER_TAP_NUM] = {…” into “static const float taps[] = {….”, that is because it is intended for PC and other high frequency CPUs instead our MCUs. Name in ‘[]’ square brackets is defined above by “#define FILTER_TAP_NUM”, so leave those brackets empty, else compiler may complain about re-definition.
#include "stm32f10x.h"
#include "delayUs.h"
#include "adc.h"
void ADCenable(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_AFIOEN; //enabling ADC clock, interrupt enable,
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV4;// ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz / 6 = 12MHz). works with div6 too
//while clock for port A and B is enabled down below
GPIOB->CRL &= ~(GPIO_CRL_CNF0_0|GPIO_CRL_CNF0_1|GPIO_CRL_MODE0_0|GPIO_CRL_MODE0_1); // pin A0 is analog input
//ADC1->CR1 |=ADC_CR1_EOCIE; //ADC interrupt enabled
//NVIC_EnableIRQ(ADC1_2_IRQn); //interrupt enabled
//ADC1->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
ADC1->SQR3 |= ADC_SQR3_SQ1_3; //for B0 in sequence 1, channel 8, it is 0b1000 = 8 (IN8)
//ADC1->SQR3 |=8; // alternative way of setting the same thing as above
ADC1->CR2 &= ~ADC_CR2_ALIGN; //data is right aligned (0bxxxx111111111111)
ADC1->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; //ADC converter is on
delay(1000); //alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...
ADC1->CR2 |= ADC_CR2_CAL;
delay(1000); //it is better to leave some time, just few clock cycles...
ADC1->CR2 |= ADC_CR2_ADON; //not sure it requires to call it again?
delay(1000); //After first ADON, ADC is just set, then second time ADC is actually enabled
}
/* For some unknown reason, DSP does not work if algorithm is included into IRQ handler, so NVIC is disabled for this IRQ */
void ADC1_2_IRQHandler(void)
{
if (ADC1->SR & ADC_SR_EOC)
{
adc_value=ADC1->DR;
}
}
int adc(void)
{
int adc=0;
ADC1->CR2 |=ADC_CR2_SWSTART;
//if(ADC1->SR & ADC_SR_EOC)
while(!(ADC1->SR & ADC_SR_EOC));
adc=ADC1->DR;
return adc;
}
#ifndef adc_h
#define adc_h
extern int adc_value;
void ADCenable(void);
int adc(void);
#endif
#include "stm32f10x.h"
void timer2enablePWM(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; //port B clock enabled (3), port A clock enable (2), Alternate IO clock enable (0)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //timer 2 clock enable (2)
GPIOA->CRL |= GPIO_CRL_CNF1_1|GPIO_CRL_MODE1_0|GPIO_CRL_MODE1_1;
GPIOA->CRL &= ~(GPIO_CRL_CNF1_0);
TIM2->CCER |= TIM_CCER_CC2E; //capture/compare timer2 output enable
TIM2->CR1 |= TIM_CR1_ARPE; //auto reload preload enable TIMxARR is buffered
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2PE; //output compare 2 mode 0b110 (14:12)?, output compare 2 preload enable
//PWM freq = Fclk/PSC/ARR 72MHz/1000
//PWM Duty = CCR1/ARR
TIM2->PSC = 0; //prescaler value, 72 MHz divided by:
TIM2->ARR = 1024; //auto reload register, value of 1024 with prescaler value 0 result in PWM frequency of 70 kHz
//TIM2->CCR2= 512; //capture/compare value, duty cycle (disabled here, enabling in call function in DSP_1.c)
TIM2->EGR |= TIM_EGR_UG; //update generation, re-initialize
TIM2->CR1 |= TIM_CR1_CEN; //counter enabled
}
void dac(int value)
{
TIM2->CCR2 = value; //here we update capture/compare value for duty cycle
}
Before anything, if you are using STM32 with ST-Link, then MCU and Si4432 is powered by ST-Link. When done with programming, you can use USB FTDI adapter as power source for 3.3V, but first move jumper from 5V to 3.3V (!). Else you can damage both, MCU and SI4432. On block diagram, this wire connection is not shown for reason that some FTDI adapter has voltage selection on bottom side, then you need to solder jumper pads to 3.3V. Here is picture with recommended setting for FTDI for use on your computer and for powering MCU with Si4432:
To get it working properly, when typing into console, console should be able to send LF and/OR CR symbol (decimal 10 or 13), so rather use HTerm, it is free for Windows and Linux. Do not use both CR and LF (CR-LF), else one of those may be sent over the radio and at receiving site one may notice moving text two rows instead one. Here is screenshot:
Default setting in ‘printMsg.c’ is 115200 baud, but you can change to your value, just comment with ‘//’ this one and un-comment (remove ‘//’) one that is the best for you. If you chose less than 9600, then whole thing will be slower than transceiver. In HTerm, there is window with COM port, if it is already open, it will NOT refresh automatically new COM port, you need to click on ‘R’ button next to window with COM port names. Enable “Newline at” and chose LF, you may disable “Show newline characters”.
Codes for remote controller
The codes below is for remote terminal, for communication between two points at fixed frequency and fixer data rate in bauds. For remote controller, the code must be modified. I will do that too, but not sure how soon. If you want it sooner, please buy me a beer. 😀 Just because I have many things to do at once, and sometimes long time may pass between page update. This will kick me to do more.
Codes:
/* This software is provided by https://wildlab.org and
Milan Karakas from Croatia. This is free program, but
also "beerware". This means if you want this beta test
phase to grow into something really great, please buy
me a beer :) https://www.paypal.me/milankarakas?locale.x=en_US
It is still in beta test phase, and will be upgraded.
BE EXTREMELY CAREFUL WITH FREQUENCY AND POWER !!!
Your module may be damaged if not made for specific freq
range. For example, 433 MHz modules from eBay may work
from say 400 - 440 MHz, but not outside that range. Also
other modules (315 MHz, 868 Mhz) works with close range.
This is because each module has different output low pass
filter, and phase shifter for the receiver. Transmitter may burn
if wrong frequency, and receiver may be insensitive due to
wrong phase on differential inputs. I warned you !!!
Also, DO NOT use small spring-looking antena that comes
with the module - this is pure crap. For 433 MHz use for
example 17 cm wire, or better solder proper coaxial cable
and proper antenna. Good antenna gives more range than power.
Later, will ad my own math to do that, but since it is still
in beta testing phase... stay tuned.
*/
#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"
//#include "string.h"
#include "spi.h"
#include "si4432.h"
int len=0;
int main()
{
usart_1_enable(); //enabling serial protocol
spiEnable(); //enabling SPI protocol
/* Be careful with the frequency of your module, and power !!! */
setModule(433.120,0); // (frequency in MHz, power in steps from 0 to 7, or 1.26 mW to 100 mW)
//enabling and initializating Si4432, setting IRQ for USART,
//setting freq (240.001-959.999 MHz), not a Hz outside (!)
//Output power:
/*
0 = +1 dBm = 1.26 mW
1 = +2 dBm = 1.58 mW
2 = +5 dBm = 3.16 mW
3 = +8 dBm = 6.31 mW
4 = +11 dBm = 12,59 mW
5 = +14 dBm = 25.12 mW
6 = +17 dBm = 50.12 mW
7 = +20 dBm = 100 mW (!)
(modem baud rate is 9600, I did not finish calculations for that, and other parameters as is type of modulation...)
(modem currently works in (G)FSK (Gaussian filtered FSK) )
(USART PC-MCU is 115200, configurable in 'printMsg.c')
*/
while(1) listenRadio();
}
/* This software is provided by https://wildlab.org and
Milan Karakas from Croatia. This is free program, but
also "beerware". This means if you want this beta test
phase to grow into something really great, please consider
some donation here:
https://www.paypal.me/milankarakas?locale.x=en_US
It is still in beta test phase, and will be upgraded.
So far it works at 433.120 MHz, and you need to change
the frequency if you wish to something else - in library
'Si4432.c', there is math and you should to calculate
and change values of the register for other frequencies.
Out there exist also Si4432 - 868 MHz module, and this
program works with this one too, but then you MUST change
the frequency, else output TX amplifier may burn.
Later, will ad my own math to do that, but since it is still
in beta testing phase... stay tuned.
*/
#include "stm32f10x.h"
uint8_t reg=0, cmd=0;
void spiEnable(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN|RCC_APB2ENR_AFIOEN; //already exist, but anyway...
/* SPI pins */
/* GPIO pin A4 is NSS ("Not" Slave Sellect), inverted... if low (0), then it is selected */
GPIOA->CRL |= GPIO_CRL_CNF4_1|GPIO_CRL_MODE4_0|GPIO_CRL_MODE4_1;// 50 MHz - NSS (nSEL on Si4432)
GPIOA->CRL &= ~GPIO_CRL_CNF4_0; //alternate function PP, this A4 pin must be high with external resistor 3kOhm to 4.7kOhm
/* GPIO pin A5 is SPI clock SCK */
GPIOA->CRL |= GPIO_CRL_CNF5_1|GPIO_CRL_MODE5_0|GPIO_CRL_MODE5_1;// 50 mhz - SCK
GPIOA->CRL &= ~GPIO_CRL_CNF5_0; //alternate function PP
/* GPIO pin A6 is MISO */
GPIOA->CRL &= ~(GPIO_CRL_MODE6_0|GPIO_CRL_MODE6_1); // INPUT - MISO
GPIOA->CRL |= GPIO_CRL_CNF6_0;
/* GPIO pin A7 is MOSI */
GPIOA->CRL |= GPIO_CRL_CNF7_1|GPIO_CRL_MODE7_0|GPIO_CRL_MODE7_1;// 50 MHz - MOSI
GPIOA->CRL &= ~GPIO_CRL_CNF7_0; //alternate function PP
/* Configuring SPI */
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;// SPI enable
SPI1->CR1 |= SPI_CR1_BR_2;//|SPI_CR1_BR_1;//|SPI_CR1_BR_1|SPI_CR1_BR_0; // 72MHz/?
SPI1->CR1 &= ~(SPI_CR1_CPOL|SPI_CR1_CPHA|SPI_CR1_DFF); // modes, 0 for polarity, and 0 for iddle clock=0, dff=0 (8 bit), lsbfirst=0 (MSB first), *
SPI1->CR2 |= SPI_CR2_SSOE; //NSS enabled
SPI1->CR1 |= SPI_CR1_MSTR;// master configuration
}
void spiWrite(int reg, int cmd)
{
SPI1->CR1 |= SPI_CR1_SPE;
SPI1->DR = (0x80|reg); //usually 0x80 does not belongs to SPI, but here it is "command mode", specific for Si4432
while (!(SPI1->SR & SPI_SR_TXE)){};
SPI1->DR = cmd;
while (!(SPI1->SR &SPI_SR_TXE)){};
while(SPI1->SR & SPI_SR_BSY);
while (!(SPI1->SR & SPI_SR_OVR)){}; //if single byte is sent,...
SPI1->CR1 &= ~SPI_CR1_SPE;
}
int spiRead(int reg)
{
SPI1->CR1 |= SPI_CR1_SPE;
SPI1->DR =reg;
while (!(SPI1->SR & SPI_SR_TXE)){};
while(SPI1->SR & SPI_SR_BSY){}; //waiting little bit longer between two sendings
SPI1->DR = 0x00; //dummy byte
while (!(SPI1->SR & SPI_SR_RXNE)){};
int rd=SPI1->DR; //dummy read? Whole thing does not working properly without
while (!(SPI1->SR & SPI_SR_RXNE)){};
int r=SPI1->DR;
SPI1->CR1 &= ~SPI_CR1_SPE;
return r;
}
#ifndef spi_h
#define spi_h
extern void spiEnable(void);
extern void spiWrite(int reg, int cmd);
extern int spiRead(int reg);
#endif
/* This software is provided by https://wildlab.org and
Milan Karakas from Croatia. This is free program, but
also "beerware". This means if you want this beta test
phase to grow into something really great, please consider
some donation here:
https://www.paypal.me/milankarakas?locale.x=en_US
It is still in beta test phase, and will be upgraded.
So far it works at 433.120 MHz, and you need to change
the frequency if you wish to something else - in library
'Si4432.c', there is math and you should to calculate
and change values of the register for other frequencies.
Out there exist also Si4432 - 868 MHz module, and this
program works with this one too, but then you MUST change
the frequency, else output TX amplifier may burn.
Later, will ad my own math to do that, but since it is still
in beta testing phase... stay tuned.
*/
#include "stm32f10x.h"
#include "delayUs.h"
#include "spi.h"
#include "si4432.h"
#include "printMsg.h"
int itStatus1=0,itStatus2=0;
int packetLength=0;
char payload[64];
void setModule(float freq, int power)
{
// freq=433.120;
int fc,fb,hbsel;
if (freq >240.00 && freq < 250.00) {fb=0;hbsel=0;}
else if (freq >250.00 && freq < 260.00) {fb=1;hbsel=0;}
else if (freq >260.00 && freq < 270.00) {fb=2;hbsel=0;}
else if (freq >270.00 && freq < 280.00) {fb=3;hbsel=0;}
else if (freq >280.00 && freq < 290.00) {fb=4;hbsel=0;}
else if (freq >290.00 && freq < 300.00) {fb=5;hbsel=0;}
else if (freq >300.00 && freq < 310.00) {fb=6;hbsel=0;}
else if (freq >310.00 && freq < 320.00) {fb=7;hbsel=0;}
else if (freq >320.00 && freq < 330.00) {fb=8;hbsel=0;}
else if (freq >330.00 && freq < 340.00) {fb=9;hbsel=0;}
else if (freq >340.00 && freq < 350.00) {fb=10;hbsel=0;}
else if (freq >350.00 && freq < 360.00) {fb=11;hbsel=0;}
else if (freq >360.00 && freq < 370.00) {fb=12;hbsel=0;}
else if (freq >370.00 && freq < 380.00) {fb=13;hbsel=0;}
else if (freq >380.00 && freq < 390.00) {fb=14;hbsel=0;}
else if (freq >390.00 && freq < 400.00) {fb=15;hbsel=0;}
else if (freq >400.00 && freq < 410.00) {fb=16;hbsel=0;}
else if (freq >410.00 && freq < 420.00) {fb=17;hbsel=0;}
else if (freq >420.00 && freq < 430.00) {fb=18;hbsel=0;}
else if (freq >430.00 && freq < 440.00) {fb=19;hbsel=0;}
else if (freq >440.00 && freq < 450.00) {fb=20;hbsel=0;}
else if (freq >450.00 && freq < 460.00) {fb=21;hbsel=0;}
else if (freq >460.00 && freq < 470.00) {fb=22;hbsel=0;}
else if (freq >470.00 && freq < 480.00) {fb=23;hbsel=0;}
else if (freq >480.00 && freq < 500.00) {fb=0;hbsel=1;}
else if (freq >500.00 && freq < 520.00) {fb=1;hbsel=1;}
else if (freq >520.00 && freq < 540.00) {fb=2;hbsel=1;}
else if (freq >540.00 && freq < 560.00) {fb=3;hbsel=1;}
else if (freq >560.00 && freq < 580.00) {fb=4;hbsel=1;}
else if (freq >580.00 && freq < 600.00) {fb=5;hbsel=1;}
else if (freq >600.00 && freq < 620.00) {fb=6;hbsel=1;}
else if (freq >620.00 && freq < 640.00) {fb=7;hbsel=1;}
else if (freq >640.00 && freq < 660.00) {fb=8;hbsel=1;}
else if (freq >660.00 && freq < 680.00) {fb=9;hbsel=1;}
else if (freq >680.00 && freq < 700.00) {fb=10;hbsel=1;}
else if (freq >700.00 && freq < 720.00) {fb=11;hbsel=1;}
else if (freq >720.00 && freq < 740.00) {fb=12;hbsel=1;}
else if (freq >740.00 && freq < 760.00) {fb=13;hbsel=1;}
else if (freq >760.00 && freq < 780.00) {fb=14;hbsel=1;}
else if (freq >780.00 && freq < 800.00) {fb=15;hbsel=1;}
else if (freq >800.00 && freq < 820.00) {fb=16;hbsel=1;}
else if (freq >820.00 && freq < 840.00) {fb=17;hbsel=1;}
else if (freq >840.00 && freq < 860.00) {fb=18;hbsel=1;}
else if (freq >860.00 && freq < 880.00) {fb=19;hbsel=1;}
else if (freq >880.00 && freq < 900.00) {fb=20;hbsel=1;}
else if (freq >900.00 && freq < 920.00) {fb=21;hbsel=1;}
else if (freq >920.00 && freq < 940.00) {fb=22;hbsel=1;}
else if (freq >940.00 && freq < 960.00) {fb=23;hbsel=1;}
fc=((freq*1e6)/(10e6*(hbsel+1))-fb-24)*64000;
/* should be interrupt for usart */
USART1->CR1 |= USART_CR1_RXNEIE; //setting only IRQ which 'listen' input of the MCU (message from PC)
NVIC_EnableIRQ(USART1_IRQn); //enabling IRQ
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN|RCC_APB2ENR_AFIOEN; //already exist, but anyway...
/* Setting output GPIO pin A2 for SDN pin on Si4432 */
GPIOA->CRL |= GPIO_CRL_MODE2_0|GPIO_CRL_MODE2_1; //pin A2 - OUTPUT - 50 MHz - SDN at Si4432
GPIOA->CRL &= ~(GPIO_CRL_CNF2_1|GPIO_CRL_CNF2_0); //not sure.... leave 00 - general purpose PP
/* Setting input GPIO pin A3 for nIRQ pin on Si4432 */
GPIOA->CRL &= ~(GPIO_CRL_CNF3_0|GPIO_CRL_MODE3_0|GPIO_CRL_MODE3_1); //pin A3 - INPUT - nIRQ at Si4432
GPIOA->CRL |= GPIO_CRL_CNF3_1; //INPUT, floating (!) Si4432 has high level if IRQs are set
/* Configuring radio */
GPIOA->BSRR = GPIO_BSRR_BS2; //Hardware reset - SDN pin goes up for 5 mS, then remains down at zero
delay(500000); //watch LEDs on GPIO 2, if there is inproper reset, then GPIO 2 will give default 1 MHz at this pin
GPIOA->BSRR = GPIO_BSRR_BR2;// pull down SDN pin on Si4432 to turn on radio
delay(17000);//wait 17 mS or more, Si4432 internal startup
/* Software reset (SW reset) */
spiWrite(0x07, 0x80); //software reset on Si4432
delay(30000);//at least 2 mS, leave it at 3 mS, else it may skip next while loop and not working properly
while((GPIOA->ODR & GPIO_IDR_IDR3)); //Waiting nIRQ to become low
//printMsg("IRQ received \n"); //debug only
spiWrite(0x0a, 0b00001111); //GPIO2 gives 32.768 kHz if line below is not included:
spiWrite(0x0d, 0b00000001); //but, lets turn off GPIO2, no frequency out if reset is properly done
/* Set the physical parameters, example freq.=433.120 MHz*/
//spiWrite(0x75,0x40|0x13); //frequency band select (0x40= 64, 0x13= 19 total= 0x53 = 83 decimal) (band select+fb value)-old for 433.120
spiWrite(0x75, ((1<<6)|(hbsel<<5 )|(fb & 0x1F))); //
// (0x75) 0 1 0 10011
// register: |reserved|sbsel|hbsel|fb[4:0]|
spiWrite(0x76,(fc>>8)); //Nominal carrier frequency 1 (0x4D = 77 decimal), fc[15:8] (fine tuning?)
//spiWrite(0x76,0x4e); //old fixed value for 433.120 MHz
spiWrite(0x77,(fc & 0xFF)); //Nominal carrier frequency 2 (0xFF = 255 decimal), fc[7:0] (fine tuning?)
//spiWrite(0x77, 0x00);//old fixed value for 433.120 MHz
/* F(carrier)= 10e6*(hbsel+1)*(N+F) */
/* F(TX)= 10e6*(hbsel+1)*(fb[4:0]+24+(fc[15:0]/64000) */
/* F=(TX) 10e6*(1+1)*(19+24+(19968/64000)= 433.120 MHz */
/* fc[15:0]=((wanted frequency in MHz/10e6*(hbsel+1))-fb[4:0]-24)*64000 */
/* Please take look at page 26 with "Frequency Band Selection", Si4432 datasheet for fb[4:0] and N values. */
/* Set desired TX data rate (9600 bps) */
spiWrite(0x6E, 0x4E); //TX data rate 1, txdr[15:8]
spiWrite(0x6F, 0xA5); //TX data rate 2 txdr[7:0] (together, txdr[15:0]= 0x4EA5, or 20133 decimal
spiWrite(0x70, 0x2C); //Modulation Mode Control 1 (0x2C= 0b00101100)
// (0x70) 0 0 1 0 1 1 0 0
// register: |reserved|reserved|txdtrtscale|enphpwdn|manppol|enmanihv|enmanch|enwhite|
/* DR_TX(bps) = (txdr[15:0]*1 MHz)/(2^(16+5*txdrtscale)) */
/* DR_TX(bps) = 20133*1e6/2^(16+5*1)= 9600.162506... */
/* txdr[15:0]= (DR_TX*(2^(16+5*txdtrtscale))/1 MHz */
/* txdr[15:0]= 9600*(2^(16+5*1)/1e6= 20132.6595, rounded to 20133, hexadecimal= 0x4EA5, 0x4E goes to txdr[15:8], 0xA5 goes to txdr[7:0] */
/* Set TX deviation register (+/- 5 kHz), or deltaF= 10 Khz, registers 0x71 and 0x72, but if fd[8] is zero, we can skip that register */
spiWrite(0x72,0x10); //for 9600 bps, 625 kHz*16= 10 kHz, part of 9 bites (!), fd[7:0], MSB bit is in register 0x71 (fd[8])
/* deltaF= fd[8=0]*625 Hz */
/* deltaF= 16*625= 10000 Hz */
/* fd[8:0]= deltaF/625 Hz */
/* fd[8:0]= 10e3/625= 16 decimal, or 0x10 hexadecimal goes to txdr[8:0] */
/* Set modem parameters according to the excel calculator for 9600 bps, deviation 45 kHz, channel filter BW: 102.2 kHz */
spiWrite(0x1C, 0x12); //IF filter bandwidth (probably the same math as with TX deviation, or maybe slightly higher value?)
spiWrite(0x20, 0xD0); //Clock recovery oversampling (?) (no any explanation in the datasheet for all things below)
spiWrite(0x21, 0x00); //Clock recovery offset 2 (?)
spiWrite(0x22, 0x9D); //Clock recovery offset 1 (?)
spiWrite(0x23, 0x49); //Clock recovery offset 0 (?)
spiWrite(0x24, 0x00); //Clock recovery timing loop gain (?)
spiWrite(0x25, 0x24); //Clock recovery timing loop gain (?)
spiWrite(0x1D, 0x40); //AFC loop gearshift override (?)
spiWrite(0x1E, 0x0A); //AFC timing control register (?)
spiWrite(0x2A, 0x20); //AFC limiter register (?)
/* Set packet structure and modulation type, set preamble length to 5 bytes */
spiWrite(0x34, 0x20); //Preamble length register, 0xA = 10 decimal, probably 1010101010 (10 bits)
spiWrite(0x35, 0x14); //Preable detection control (threshold 42 bits for (G)FSK with AFC enabled)
spiWrite(0x33, 0x02); //disalbe header bytes, Header control 2
spiWrite(0x36, 0x2D); //2D is sync word 3 (0b00101101)
spiWrite(0x37, 0xD4); //D4 is sync word 2 (0b11010100), sync word 0 not used, so register 0x38 is omitted
/* Enable TX & RX packet handler and CRC-16 (IBM) check */
spiWrite(0x30, 0x8D); //Data access control register
// (0x30) 1 0 0 0 1 1 01
// register: |enpacrx|lsbfirst|crcdonly|skip2ph|enpactx|encrc|crc[1:0]|
spiWrite(0x32, 0x00); //Header control 1 (disabling receive header filters)
spiWrite(0x71, 0x63); //Modulation mode control 2
// (0x71) 01 10 0 0 11
// register: |trclk[1:0]|dtmod[1:0]|eninv|fd[8]|modtyp[1:0]|, fd[8] is MSB for fd[8:0] that can be found in reg. 0x72
spiWrite(0x0B, 0b00010010 ); //GPIO 1 (set TX state) was 0x12
spiWrite(0x0C, 0b00010101); //GPIO 2 (set RX state) was 0x15
/* Non default Si443x registers */
spiWrite(0x09, 0xA9); //Crystal oscillator load (Leave it as-is. Or, you can fine tune 30 MHz oscillator if you have precise freq.meter)0xAA
spiWrite(0x69, 0x60); //Override 1 register (?)
// (0x69) 0 1 1 0 0000
// register: |reserved|sgi|agcen|lnagain|pga[3:0]|
/* Enable receiver chain */
spiWrite(0x07, 0x05); //Operating function control 1 reg.
// (0x07) 0 0 0 0 0 1 0 1
// register: |swres|enlbd|enwt|x32ksel|txon|rxon|pllon|xton|, enabling only RX and Xtall (30 MHz crystal)
spiWrite(0x05, 0x03); //Interrupt enable 1 ('ipkval', and 'encrcerror') valid packet received
// (0x05) 0 0 0 0 0 0 1 1
// register: |enfferr|entxffafull|entxffaem|enrxffafull|enext|enpksent|enpkvalid|encrcerror|
spiWrite(0x06, 0x00); //Interrupt enable 2 (actually not enabling anything, just in case you need RSSI, then use 'enrssi')
// (0x06) 0 0 0 0 0 0 0 0
// register: |enswdet|enpreaval|enpreinval|enrssi|enwut|enlbd|enchiprdy|enpor|
/* Read interrupt status registers */
itStatus1=spiRead(0x03);; //read Interrupt status 1
itStatus2=spiRead(0x04);; //read Interrupt status 2
//printMsg("itStatus1= %d, itStatus2= %d\n",itStatus1,itStatus2);
/* Power settinigs */
spiWrite(0x6D, (power&0x07)); //Power, from 0 to 7, max power is 100 mW
// (0x6D) 0 0 0 0 0 000
// register: |reserved|reserved|reserved|reserved|lna_sw|txpow[2:0]|
}
void USART1_IRQHandler(void)
{
USART1->CR1 &= ~USART_CR1_RXNEIE; //as soon as it enters IRQ, forbid re-triggering IRQ
len=readMsg()+1;
int fraction;
/* Sending message just received over USART - lets disable receiver first and clear TX buffer */
spiWrite(0x07, 0x01); //disabling receiver
fraction = ((len)%64); //we should to know how how many packets of 64 + the rest of bytes...
if ((len)<65)
{
spiWrite(0x08, 0x01); //clearing TX FIFO buffer
spiWrite(0x08, 0x00); //removing clearing bit
for (int s=0;s<(len);s++)
{
spiWrite(0x7F, buff[s]);
}
spiWrite(0x3E, (len));
spiWrite(0x05, 0x04); //interrupt 1 enable ('enpktsent')
spiWrite(0x06, 0x00); //interrupt 2 enable (actually nothing enabled)
spiWrite(0x07, 0x09); //Sending packet of 64 bytes
while((GPIOA->IDR & GPIO_IDR_IDR3)){}; // waiting for confirmation that packet is sent, nIRQ goes low if done
//printMsg("small packet sent %d\n", len);
}
else
{
for (int p=0;p<(len-fraction);p+=64)
{
spiWrite(0x08, 0x01); //clearing TX FIFO buffer
spiWrite(0x08, 0x00); //removing clearing bit
for (int b=0;b<64;b++)
{
spiWrite(0x7F, buff[b+p]);
}
spiWrite(0x3E, 64); // If it gets here, packet length will be always 64 bytes long, so no need to put calculation or anything else
spiWrite(0x05, 0x04); //interrupt 1 enable ('enpktsent')
spiWrite(0x06, 0x00); //interrupt 2 enable (actually nothing enabled)
spiWrite(0x07, 0x09); //Sending packet of 64 bytes
while((GPIOA->IDR & GPIO_IDR_IDR3)){}; // waiting for confirmation that packet is sent, nIRQ goes low if done
itStatus1=spiRead(0x03);; //read Interrupt status 1
itStatus2=spiRead(0x04);; //read Interrupt status 2
//printMsg("Part of the packet sent= %d num= %d\n",(temp+1), p);
}
spiWrite(0x08, 0x01); //clearing TX FIFO buffer
spiWrite(0x08, 0x00); //removing clearing bit
for (int f=0;f<fraction;f++)
{
spiWrite(0x7F, buff[((len)-fraction)+f]);
}
spiWrite(0x3E, fraction);
spiWrite(0x05, 0x04); //interrupt 1 enable ('enpktsent')
spiWrite(0x06, 0x00); //interrupt 2 enable (actually nothing enabled)
spiWrite(0x07, 0x09); //Sending packet of 64 bytes
while((GPIOA->IDR & GPIO_IDR_IDR3)){}; // waiting for confirmation that packet is sent, nIRQ goes low if done
//printMsg("remaining packet sent %d\n",fraction);
}
USART1->CR1 |= USART_CR1_RXNEIE; //after everything done here, re-enable IRQ
}
void listenRadio(void)
{
/* Enable receiver chain and listening RX*/
spiWrite(0x08, 0x02); //operating function control 2 reg. clearing TX and RX FIFO buffer, register bit 'ffclrrx'
spiWrite(0x08, 0x00); //operating function control 2 reg. not sure why it should go back to zero, but it must be tehere
/* Set interrupts for valid packet ('ipkval'), or CRC error ('encrcerror') */
spiWrite(0x07, 0x05); // RX on
/* Wait for interrupt event */
while((GPIOA->IDR & GPIO_IDR_IDR3)); //if nothing received, then it will "stuck" here forever
spiWrite(0x05, 0x03); //interrupt enable 1 'ipkval' and encrcerror'
spiWrite(0x06, 0x00); //interrupt enable 2, but this time nothing is enabled
// RSSI value is in register 0x26, and threshold for "clear RSSI" in 0x27 (RSSI is not zero when no signal, there is some noise)
spiWrite(0x07, 0x01); //disabling receiver during received packed testing, but leave xtall on
/* Checking for errors first */
itStatus1=spiRead(0x03);; //read Interrupt status 1
itStatus2=spiRead(0x04);; //read Interrupt status 2
//printMsg("* itStatus1= 0x%X, itStatus2= 0x%X \n",itStatus1,itStatus2); //debug only
/* if CRC error interrupt occured ? : disabled because from time to time some RF noise trigger it without 'real' transmission at the other end.
if ((itStatus1 & 0x01) ==0x01) // 0x01 = 'icrcerror' in register 0x03 on Si4432
{
printMsg("CRC error \n");
}
*/
/* Valid packet received interrupt occured ? */
if ((itStatus1 & 0x02) == 0x02) // 0x02 = 'ipkvalid' in register 0x03 on Si4432
{
packetLength=spiRead(0x4B); //read length of the recieved packet
//printMsg("received packet length= %d \n", packetLength); //debug only
for (int i=0;i<packetLength;i++)
{
payload[i]=spiRead(0x7F); //read FIFO
printMsg("%c",(char)payload[i]); //print character by character
}
//printMsg("\n"); //Line Feed (LF) no needed, since we sending it over the radio
}
spiWrite(0x08, 0x02); // clearing TX and RX FIFO buffer, register bit 'ffclrrx'
spiWrite(0x08, 0x00); // not sure why it should go back to zero, but it must be tehere
}
/* This software is provided by https://wildlab.org and
Milan Karakas from Croatia. This is free program, but
also "beerware". This means if you want this beta test
phase to grow into something really great, please consider
some donation here:
https://www.paypal.me/milankarakas?locale.x=en_US
It is still in beta test phase, and will be upgraded.
So far it works at 433.120 MHz, and you need to change
the frequency if you wish to something else - in library
'Si4432.c', there is math and you should to calculate
and change values of the register for other frequencies.
Out there exist also Si4432 - 868 MHz module, and this
program works with this one too, but then you MUST change
the frequency, else output TX amplifier may burn.
Later, will ad my own math to do that, but since it is still
in beta testing phase... stay tuned.
*/
#include "stm32f10x.h"
#include "stdint.h"
#include <stdio.h>
#include "stdarg.h"
#include "string.h"
#include "printMsg.h"
#define buffer 20000
char buff[buffer]; //10000 characters! If you need less, then change this value AND (!) value value below as well
void usart_1_enable(void)
{
//enabling pin A9 for alternating funct. for uart/usart
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN; //clock to GPIO A enabled, port A(2), alt.funct.en(0), usart1 clock enabled(14)
GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1; //port A9 TX
GPIOA->CRH &= ~GPIO_CRH_CNF9_0; //port A9
GPIOA->CRH &= ~(GPIO_CRH_MODE10_0|GPIO_CRH_MODE10_1); //port A10 is RX
GPIOA->CRH |= GPIO_CRH_CNF10_0; //port A10 is RX
//GPIOA->CRH = 0x444444B4; // A9 is alternate output, 50 MHz, push-pull - not this time short version (!)
//clkPer/(baudRx_16bit)=72MHZ/9600 = 7500 = 0x1D4C
/* Remove comment line for speed that you want to use*/
//USART1->BRR = (0xEA60); // 1200 Baud
//USART1->BRR = (0x7530); // 2400 Baud
//USART1->BRR = (0x3A98); // 4800 Baud
//USART1->BRR = (0x1D4C); // 9600 Baud
//USART1->BRR = (0x1388); // 14400 Baud
//USART1->BRR = (0xEA6) ; // 19200 Baud
//USART1->BRR = (0x9c4) ; // 28800 Baud
//USART1->BRR = (0x753) ; // 38400 Baud
//USART1->BRR = (0x505) ; // 56000 Baud
//USART1->BRR = (0x4E2) ; // 57600 Baud
USART1->BRR = (0x271) ; // 115200 Baud
//USART1->BRR = (0x232) ; // 128000 Baud
//USART1->BRR = (0x119) ; // 256000 Baud
//USART1->BRR = (0x8C) ; // 512000 Baud
//USART1->BRR = (0x46) ; // 1024000 Baud
//USART1->BRR = (0x23) ; // 2048000 Baud
//USART1->BRR = (0x18) ; // 3000000 Baud (3 MHz, max speed that HTerm can get, non-standard speed)
USART1->CR1 |= USART_CR1_TE; //transmitter enable
USART1->CR1 |= USART_CR1_RE; //receiver enable
USART1->CR1 |= USART_CR1_UE; //usart enable
}
void printMsg(char *msg, ...)
{
//char buff[120]; //was 80
va_list args;
va_start(args,msg);
vsprintf(buff,msg,args);
for(int i=0;i<strlen(buff);i++)
{
USART1->DR = buff[i];
while(!(USART1->SR & USART_SR_TXE)); //wait for TXE, 1 = data transferred
}
}
int readMsg(void)
{
for( len =0;len<buffer;len++) //20000 characters! If you need less, then change this value AND (!) buff[] value above as well.
{
while(!(USART1->SR & USART_SR_RXNE));
buff[len]= USART1->DR;
if (buff[len]==10 || buff[len]==13) break; //if enter is pressed, providing that it is 10 or 0xA (line feed), or 13 (0xD) (carriage return)
}
return len; //just returning number of entered characters, not including line feed nor carriage return (!)
}
EEPROM sounds intimidating for the beginners, probably because there are few rules to comply. First, all EEPROMs share the same address on I2C bus, at least first page, and that is 0x50. I will give example for Atmel 24C08 chip, which has 8 kbit (!) memory. This number is NOT killo-bytes, but 1024 x 8 bits. So, practically ‘only’ 1 KB of memory space. Second rule is that writing must be done in sequence(s) of 8 or 16 bytes, depending of memory type. 1k and 2k EEPROMs can write only 8 bytes at a time, but 4k/8k/16k can write 16 bytes at a time. Between each write cycles and write then read cycle should be about 2 mS delay. This delay is some intrinsic property of the memory, and we can’t do anything about that. Only follow the rule. Read is possible whole ‘page’ of 256 bytes at once. Also, there is no restriction between two readings. Only after writing even singly byte, must be some delay, experimentally found 1.68 ms, so better use 2 mS (2000 uS) for sure.
Splitting data into groups of 16 bytes
That is how it should works. I made relatively simple code for STM32f10x family of the MCUs. In this code, there is two examples, one writing just 16 bytes, another one writing more than that in few steps with delay of 2 mS between each ‘packets’ of 16 bytes. Second example uses second of four pages. First example is on first page. Each page has actually its own I2C address ranging from 0x50 to 0x57 for 16k EEPROMs. I have only one chip that has 8k, so it covers four pages; page 0 = 0x50, page 1 = 0x51, page 2 = 0x52, and page 3 = 0x53. I found this chip below board with STM32f103VET6, that was surprise for me. Did not found any data about that board, nor it is mentioned in STM32 literature. And since this STM32 board has no ‘name’ as is for example Arduino uno, no data about this one except few words on eBay (plus price tag 😀 ).
In the example code I did not make algorithm for writing whole chip, because in practice this type of memory is just for few variables, maybe some calibration data or whatever user need to change after programming MCU, or during. For example, some servo has offset where middle position is not exactly in the middle. So, we can make code that scan buttons which moves servo, and when servo is where we want to be, another button press save calibration data into EEPROM. Since I did not use this chip in the past, I can’t give any example for now, but for sure it will be here in the future.
Code(s) not complete. Why?!
Please look carefully the examples. First example is not implemented correctly. I have doubt – do I need finish everything to show you, or you can learn something and recognize how to solve ‘the puzzle’? Second example, just un-comment (remove ‘//’) two separate functions twiSend(), twiReceive() and one printMsg() . That is last printMsg() which read all 255 bytes from second page at 0x51. Also, you may notice that there are three strange variables included: ‘num’, ‘mantissa’ and ‘fraction’. Variable ‘num’ uses function strlen(test2) to get number of characters needed for two ‘for(;;)’ loops. In for example we have 92 characters, then 92/16 =5.75. Mantissa is number 5 (currently no needed in those examples), 0.75 is fraction, but (!) expressed in remaining bytes, that is 0.75*16=12.
Very interesting first loop:
for (int p=0;p<(num-fraction);p+=16)
This one uses number of characters (for example 92), subtract fraction (say 12), then it goes NOT from 0 to 92, but from 0 to 80 in steps of 16. Then some conversion of characters into uint8_t form. Not ideal, but… Then function twiSend(0x51,p,16) sends first 16 bytes, then another 16 until reaches 80. Then it exits for(;;) loop, and send the remaining 12 bytes twiSend(0x51,(num-fraction),fraction). At this time, ‘num-fraction’ is 92-12=80, which means that it begins to write at position 80 in EEPROM memory, for next ‘fraction’, which is 12 bytes. After you copy/paste those codes, please align everything, because operation copy/paste onto this page can ruing alignment.
Here are the codes:
/*placeholder for page URL
This is just an example. You can modify this code (it is free)
as you wish and adapt to your needs.
*/
#include "stm32f10x.h"
#include "printMsg.h"
#include "wire.h"
#include "delayUs.h"
#include "string.h"
//uint8_t address=0;
uint8_t buffer[1024];
int main()
{
usart_1_enable(); //enabling printMsg();
twiEnable();//enabling two wire interface twiEnable(); that is our I2C1
/*Next function wroks only if you #include "printMsg.h" in library (also #include wire.h) wire.c
(is included when you include .h lib.), there is already printMsg() function that will send to
USART1 message about addresses that is found on the I2C bus (here, only I2C1 bus). Note that
some STM32 boards has already EEPROM (8 Kbit) on the bottom of the PCB. Example is STM32F103VET6 */
//twiScan();//before anything else, lets check which devices are on the bus,
/* EEPROM example writting and reading 16 bytes (4K/8K/16K only) at first page (0x50) with some message */
char test[]={"This is for test"}; //test write to EEPROM, 1K/2K only 8 bytes, 4K/8K/16K maximum 16 bytes at once
// 1234567890123456 (helper to see when it 'fit' into 16 bytes for 4K/8K/16K EEPROMs, can be less than 16 bytes, but not more)
for (int i=0;i<strlen(test);i++)
{
buffer[i]=test[i]; //filling (uint8_t)buffer[] with (char)test
}
//twiSend(0x50,0,strlen(test)); //sending first 'packet' to the EEPROM at address 0x50 from position 0
delay(2000); //minimum time to wait is 1.658 mS, so use 1 mS, or better 2000 uS, else it will stuck
//twiReceive(0x50,0,40); //receiver from first page at 0x50
for (int i=0;i<strlen(test);i++)
{
//printMsg("%c",buffer[i]);//here %c means that we will print characters. If you want, you may try %c and you will get ASCII values
}
/* EEPROM example writing and reading more than 16 bytes in few sequences*/
char test2[]={"This is example of writing EEPROM memory more than 16 bytes - in sequences of 16 bytes + 12 ."};
// 12345678901234567890123456789012345678901234567890112345678901234567890123456789012345678901234567890
// | 10 | 20 30 | 40 |50 60 | 70 8|0 90 | 100... (decades)
// 0 16 32 48 64 80 96... (hexadecimals)
//int mantissa;
int fraction;
int num;
num=strlen(test2);
//mantissa=num/16;
fraction=num%16;
for (int p=0;p<(num-fraction);p+=16)
{
for (int b=0;b<16;b++)
{
buffer[b]=test2[b+p];//lets put packets of 16 bytes into buffer
}
twiSend(0x51,p,16); //second page (0x51), but you can do it at any other pages from 0x50 to 0x57 (if your EEPROM has that much memory)
delay(2000); //wait 2 mS between two EEPROM access
}
for (int i=0;i<fraction;i++)
{
buffer[i]=test2[(num-fraction)+i];
}
delay(2000); //wait 2 mS between two EEPROM access
twiSend(0x51,(num-fraction),fraction);
delay(2000);
//twiReceive(0x50,0,120); //receiving bytes stored in EEPROM in the first example written way above, first page
//for (int i=0;i<255;i++) printMsg("%c",buffer[i]); printMsg("\n");
delay(2000);
twiReceive(0x51,0,255); //receiving bytes stored in EEPROM in the second example above, second page
for (int i=0;i<255;i++) printMsg("%c",buffer[i]); printMsg("\n");
}
/* Two Wire Interface, I2C (or IIC), here will be called 'twi', and we have
only twiEnable(), twiSend() and twiReceive(). The twiSend() function is
fairly simple, we just send address of the device shifted to the left by
1 bit, or-red | zero (0) at free space that tell I2C bus it is for write operation.
The receive twiReceive() function works by sending address also shifted left
one bit with logic or | zero (0) at empty bit (LSB), but then we must send command
to the device depending what device has. After command, we stop (although
we can remove STOP condition and continue to "repeated start", then we
must change bit after address of the device, now it is one (1) that tells
I2C bus we want to read. If we try only read from some address, device
don't know what to send. So we must first issue command, then read. For
specific command set read datasheet of particular device - it is different
for all different devices. More on my website: http://wp.me/p7jxwp-nD */
#include "stm32f10x.h"
#include "delayUs.h"
#include "wire.h"
#include "printMsg.h"
void twiEnable(void)
{
//just set all registries, but NOT START condition - execute once in main.c
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; //B port enabled, alternate function
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //I2C 1 enabled
GPIOB->CRL = 0xFF000000;// setting just pins B7 (SDA) and B6 (SCL), while leaving the rest intact 50 MHz!
I2C1->CR2 |= 50; // GPIO clock freq=50 MHz MUST !!! be equal APB frequency (GPIO, 2, 10 or 50 MHz)
I2C1->CCR |= I2C_CCR_FS; //fast mode
I2C1->CCR |= 30; //not sure for 400 000 - (10= 1.2 MHz, 15=800 kHz, 30=400 kHz)
I2C1->TRISE |= 51; // maximum rise time is 1000 nS
I2C1->CR1 |= I2C_CR1_PE;
}
void twiScan(void)
{ int a=0;
for (uint8_t i=0;i<128;i++)
{
I2C1->CR1 |= I2C_CR1_START;
while(!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR=(i<<1|0);
while(!(I2C1->SR1)|!(I2C1->SR2)){};
I2C1->CR1 |= I2C_CR1_STOP;
delay(100);//minimum wait time is 40 uS, but for sure, leave it 100 uS
a=(I2C1->SR1&I2C_SR1_ADDR);
if (a==2)
{
printMsg("Found I2C device at adress 0x%X (hexadecimal), or %d (decimal)\n",i,i);
}
}
}
/* Command or commands, or sending bytes, just the same name of the variable 'command' */
void twiSend(uint8_t address, uint8_t command, uint8_t length)
{
I2C1->CR1 |= I2C_CR1_START; //START condition
while(!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR=(address<<1|0); //sending address of the device, 0 = sending
while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2));
I2C1->DR=command; //filling data register with byte, if single - command, multiple - command(s) and data
for (uint8_t i=0;i<length;i++)
{
I2C1->DR=buffer[i]; //filling buffer with command or data
delay(60);
}
I2C1->CR1 |= I2C_CR1_STOP;
}
void twiReceive(uint8_t address, uint8_t command, uint8_t length)
{
I2C1->CR1 |= I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_START; //start pulse
while(!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR=(address<<1|0); //sending address of the device, 0 = sending
while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2 & I2C_SR2_BUSY));
I2C1->DR=command; //sending command to the device in order to request data
I2C1->CR1 |= I2C_CR1_START; //REPEATED START condition to change from sending address + command to receive data
while(!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR=(address<<1|1); //sending address of the device, 1 = reading
while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2));
if (length==1) //receiving single byte, N=1
{
while(!(I2C1->SR1)|!(I2C1->SR2));
I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) to signal it is last byte
I2C1->CR1 |= I2C_CR1_STOP; //issuing STOP condition before (!) reading byte
buffer[0]=I2C1->DR; //single byte is read AFTER NAK (!) and STOP condition
}
if (length==2) //receiving two bytes, N=2
{
while(!(I2C1->SR1)|!(I2C1->SR2));
I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) before last byte
I2C1->CR1 |= I2C_CR1_STOP;
buffer[0]=I2C1->DR; //reading N-1 byte, next to last byte is in DR, last one still in shift register
while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
buffer[1]=I2C1->DR; //read last N byte now available
}
if (length>2) //receiving more than two bytes, N>2
{
for (uint8_t i=0;i<length;i++)
{
if (i<(length-3)) // if it is not N-2, then read all bytes
{
while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
buffer[i]=I2C1->DR;
}
else if (i==length-3) // if it is N-2 then read
{
while(!(I2C1->SR1)|!(I2C1->SR2));
buffer[i]=I2C1->DR;
while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) before last byte
I2C1->CR1 |= I2C_CR1_STOP;
}
else if (i==length-2) // if it is N-1 then read
{
while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
buffer[i]=I2C1->DR;
}
else if (i==length-1) // else it is N byte
{
while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2)){};
buffer[i]=I2C1->DR;
}
}
}
}
void delay(unsigned long cycles)
{
while(cycles >0)
{
asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
asm("nop");asm("nop");asm("nop"); //to get 1 uS if delay(1)
cycles--;
}
}
#ifndef delayUs_h
#define delayUs_h
extern void delay(unsigned long cycles);
#endif
Copy/paste all codes and save in the same directory for Keil. I am not sure but I think the same codes can work in other editors/compilers/assemblers, but I am not familiar with those.