Introduction: Pulse Generator
When you need pulses of any sort, long, short and with variable frequency you can use one or two 555 timers. That works, is easy to set up and is cheap. But it also limited to a certain range in frequency and pulse length.
If you want a precise pulse length you will need an oscilloscope to check it. The same goes for when you want an exact frequency.
I needed pulses of exactly 1us, 5us, 1ms and 5ms, I decided that trying to do that with 555 timers was not what I wanted.
So I build a versatile pulse generator with a microcontroller and a crystal to produce exact results and a display to show the current values for frequency and pulse length.
EDIT: The code is now available on GitLab
https://gitlab.com/WilkoL/pulse_generator_st7735
Supplies
STM8S103F3 on development board
16 MHz crystal
ST7735 1.8 inch TFT display
74HC14
rotary encoder (with button)
projectbox
perfboard
STMicroelectronics ST-Visual_Develop (IDE)
Cosmic Software STM8 compiler
ST-Link-V2 (or a clone)
manuals about the STM8S103
coffee
Step 1: The STM8S103F3
Usually I pick a STMicroelectronics STM32 or an Atmel (now Microchip) ATMEGA or even ATTINY microcontroller. This time I decided to use a STM8 microcontroller, the STM8S103F3. You can buy them on a breakout board for less than a euro.
While it is less capable than even the lowest of the STM32 series, it is somewhat comparable with the ATMEGA328 in speed and number of pins. But it only has a quarter of flash memory, just 8kByte.
The peripherals on the other hand are more capable than those in the ATMEGA328, esspecially the timers are awesome. And those are exactly what is needed for a pulse generator.
Step 2: IDE and Compiler
I program the STM8 in C with STMicroelectronics free "ST Visual Develop" (STVD) and the, also free, STM8 compilers from Cosmic-Software. (to download and use these programs you do need to register with their respective companies) Both programs feel rather old fashioned but they work well.
Most (all?) microcontrollers have pins with many alternate uses, they can be standard inputs or outputs, but can also be connected to one or more peripherals in the microcontroller. The selection of what alternate use a pin can have is dne with "Option Bytes" in a STM8S103F3. It can be done when the code is uploaded to the microcontroller but I prefer to use a separate program to do that called ST Visual Programmer, the option byte needed here is AFR0 (alternate function bye 0) see the screen print of the option bytes.
To upload the code into the microcontroller and to program the option bytes I have an official ST-Link-V2 but you can just as well use an incredibly cheap clone (3 euro).
Then there is STM8CubeMX. If you know the STM32 version of this program you will be disappointed with this program. The only real use of it is to make a layout of what pins get what function. You cannot produce any code with it as you can with STM32CubeMX.
Step 3: Standard Peripheral Library / Bare Metal
At first I used the Standard Peripheral Library (SPL) from STMicroelectronics, but I ended up coding on "the bare metal" as it is called. This means that instead of writing
GPIO_WriteHigh(GPIOC, GPIO_PIN_0);
you write
GPIOC->ODR |= GPIO_PIN_0; .
It does not make the code much smaller as the SPL is well written, but when the code was getting bigger, it didn't fit in the available flash of the microcontroller when I used the SPL, but it did fit when written without the SPL. (in the finished program there are just a few bytes left)
The pictures show the configuration of the clock via the SPL and the Bare_Metal method. It takes a little time to get used to it, but it isn't hard.
Step 4: Change of Mind
What also changed during development was the choice of display. I started with a LCD16x2 display but changed to a 1.8 inch ST7735 TFT. The main reason for this change was the size of the display, the LCD16x2 did fit in the project box I had in mind, but it would have used most of the frontpanel of it. The ST7735 is smaller so it made the layout of the frontpanel (if you can call it that) easier. As a bonus you can display a more text on it. Porting the drivers for the ST7735 from another project, with another type of microcontroller, proved to be easy.
Step 5: User Interface
A pulse generator doesn't need a complicated user interface, in this case a single rotary encoder is enough. When the generator is switched on for the first time, the rotary encoder manages the frequency, press it once and the pulse width is changed.
There is a problem with being able to set the frequency and pulse width separately. Many combinations simply cannot work, you cannot produce pulses of 1 second length 50 times per second. I chose to make the maximum pulse length dependent on the frequency. (the other way round is also possible). This maximum possible pulse length is always shown. When you try to set a longer pulse length it ignores it. When you set a pulse length and increase the frequency it will do so until it reaches the maximum possible frequency at that pulse length and it changes the function of the rotary encoder from frequency to pulse length. There you can then decide to shorten it or leave it as it is, lengthening isn't possible of course.
Step 6: Timers
The STM8S103F3 has three timers, a basic timer, and general purpose timer and an advanced timer. The advanced timer (TIM1) can be used as a One-Pulse timer, just what is needed. When a One-Pulse timer is triggered it counts from zero to the value set in the Auto Reload Register (ARR) and then stops, until it receives another trigger.
TIM1 has four input/output channels of which one is an input used to trigger the timer and the second channel is the output for the pulse-output. Unfortunately it does not have the option to trigger it iternally from another timer, that option is reserved for bigger versions of this microcontroller, that have more timers. Not that it matters in this project, it would only have made one connection from one pin to the next pin unneccessary, but I like the idea of keeping as much as possible inside a microcontroller.
The general purpose timer (TIM2) is used to generate the frequency that triggers TIM1. While TIM1 has a prescaler that divides the clock frequency (16MHz) by any integer from 1 to 65536, TIM2 only has a prescaler that can be set to powers of two (1, 2, 4, 8, 16, 32 .. 32768). Only one output of TIM2 is used and is (as said) on the outside of the microcontroller connected to the input of TIM1.
Many frequencies can be generater with good accuracy or even exact values, but not all. One example: to create a frequency of 700 kHz from a clock of 16 MHz you need to divide it by 22.857, that simply isn't possible with TIM2, you can divide by 23 to produce 695 kHz or divide by 22 which gives 727 kHz, I chose to produce 695 kHz as that is closest by. The next frequency, 800 kHz, can be produced exactly again. In the low frequencies ranges the error for "odd" frequencies, such as 30 Hz, is so small you can ignore it.
With the pulse length this problem is not there as both the prescaler and the Auto Reload Register of TIM1 can be set to any integer from 1 to 65536. But there is another problem with the shortest pulse lengths, the timer isn't infinitely fast internally, it takes time to switch the output from 0 to 1 and back. This time is short, just 1 clock cycle, but that is 62.5ns and when the pulse you want is 300ns it is a big error. My solution is very simple, for the pulse lengths shorter than 1us the display shows the selected value plus 63ns. This way the display and the real pulse length are almost exactly equal.
Step 7: Calculations
In LibreOffice Calc I made tables with the needed prescalers (PSCR) and highest numbers the timers can count to (ARR). I also made a table to find what the maximum pulse length could be. These tables you can find in the files tables.h and tables.c . The maximum settings I did not put into these files but handled in a switch-case statement, as it turns out the be very regular how much the maximum pulse length needs to be relative to the current frequency.
I won't bother you with the calculations, the result can be seen in the file tables.c
Step 8: Code and How I Handle the Hardware
The rotary encoder and it button are read via GPIO ports that are configured as external interrupt sources (EXTI). The interrupt routines are very simple, as soon as they are called they disable themselves to prevent bounce, they set a timeout after which they are re-enabled and set variables "button" and "encoder" with the current value. For button that is a simple "1" when it is pressed, for the rotary encoder it is a "-1" or "1" depending on the rotation.
The third timer (TIM4) is used for handling timeouts, it generates 1000 interrupts per second, it counts down the timeout set in the iterrupt routines of the rotary encoder and its button and does the re-enabling of those interrupts.
As the timer TIM1 and TIM2 use some GPIO that cannot be changed to other pins, the SPI peripheral (for sending data to the display) could no longer be used. This meant that instead of passing on the data to the SPI peripheral I had to make the SPI signals by bitbanging them. Bitbanging SPI is easy but it isn't as fast as using a dedicated peripheral. You can see that when you switch on the generator, it takes some time to clear the screen. Fortunately the amount of data that is send to the display in normal use is small so the performance is acceptable.
The output of the microcontroller is fed into a 74HC14, a hex inverter, the first inverter is connected to the microcontroller, its output goes to the inputs of all five other inverters. Of those five, three have the outputs connected with 150 ohm resistors to the output connector. This gives a nice low impedance output signal and at the same time protects the 74HC14 from a shorted output. The other two inverter ouputs are not-connected
The schematic doesn't show the power supply, it is a 3.3V power supply made with a Holtek Semiconductor 7333 low dropout regulator and a lithium ion battery. The battery is "hidden" under the display.
Attachments
Step 9: Result and Video
The result is a generator that can produce pulses from as high as 2 MHz down to 1 pulse every 100 seconds.
The pulse length can be set from 250 ns up to 90 seconds. Of course the limitations mentioned above apply, you can set a pulse with length 250 ns to appear every 100 seconds, but you cannot make a pulse of 10 seconds appear a 1000 times per second.
As the timers, once set, are independent of any other code running on the microcontroller, the signals are absolutely clean and regular.
The picture shows an extreme setting, one pulse of 250ns every 100s.

Runner Up in the
Build a Tool Contest
33 Comments
Question 1 year ago on Step 6
Hi, and thanks for this lovely program. I'd like to build it using Arduino because I am coding on a Macintosh, any suggestion for a Arduino module to choose on?
Best regards, Alain.
Answer 1 year ago
Hi Alain,
You will have to use a faster microcontroller than the standard ATMEGA328 because the Arduino version of C++ is rather slow. I do not know if there already is Arduino support for the Raspberry Pico microcontroller but if there is, it will be fast enough. Perhaps even faster.
Succes,
Wilko
Reply 1 year ago
Hi WilKoL, thanks for your answer, I'll take a look at the adafruit RP2040 ; not costly and a real high speed clock. It seams Arduino did the job since last year, I'll take a look at it!
Best regards,
Alain.
2 years ago
Hello, I would like to use the software as base for another project. Basically I would like to re-assign some of the pins to different signals. Is it possible that both encoder pins and the button are connected to the same port, so I would like to shift encoder from Bort B to D and button from A to D too. As far as I understand your code with the external interrupt this may not be possible, isn't it. So basically I can also assign button and encoder to different ports as you have done but would be from interest if there is a way that all three lines share the same port. Regards Andreas
Reply 2 years ago
I hope that I understand your question when I say, you want to move all pins of the rotary encoder to PORTD. They will then be connected to, for example PD2, PD3 and PD4.
This means that all activity of the rotary encoder must be handled in one interrupt routine.
I think that is possible, but you will have to check the status of all GPIO inside of that interrupt routine and then decide what action is needed. It will make things more difficult but not impossible.
I don't know what other things you want to do with the code but remember that you cannot move any peripheral to any GPIO. If I really needed more GPIO I would move from the STM8S103F3 to a STM8S103K3
Reply 2 years ago
Thanks for your answer. I think I understand what you wrote regarding encoder and button sharing the same Port. I have only moved encoder Pins to Port D and left button in Port A.
The Idea of my Project ist a DAC Control for an Electronic load. So basically the Pin constraints regarding Timer do not exist. But I need I2c Pins for a Sensor. What I can Tell ist that i could successfully move the Display to Other Port Pins. But you are right looking for a STM8 with more pins would be a good Idea, but i will give the STM8S103F3 a go for this Job.
Now i have found that the sprintf Routine requires > 1,5 to 2kbyte Code Size. So far i could see only Long int is Converted into String. So you know If ltoa exist for cosmic Compiler? Maybe there are other functions for conversion available with less memory usage than sprintf.
Reply 2 years ago
First: Instructables is VERY slow with sending notifications, I received an email about your reply on the 28th of Januari!!!
Ah, sprintf()... Yes, I found that problem too. And no, there is no itoa() or ltoa() available. So I had to make one myself, take a look here:
https://gitlab.com/WilkoL/wakeup_light_stm8s103
In de lib folder you will find an itoa.h and itoa.c
Good luck.
3 years ago
Hello,
thanks for sharing this project. Unfortunately there are no many projects outside using the STM8 family. So I appreciate your work! Still I have issues to get the code compiled, I have some experience with SDCC but not with ST Visual Studio and COSMIC compiler, even though already installed. Which files except those you provided do I need? I have seen the stm8s.h in one of your sceenshots. I found this file within Standard Peripheral Library (SPL) from ST. Another include file is claimed: stm8s_conf.h. If I add this file I get tons of errors regarding missing peripheral include files. Could you please give some advice how to proceed. Thanks in advance! Regards Andreas
I've got further by comment out the claimed lines of code in stm8s_conf.h.
The only thing was that one symbol was defined within two files stm8_interrupt_vector.c and stm8s_it.c. So after some trials I removed the stm8s_it.c file from project.Hopefully this was the right decision.
Now the next issue:
"#error clnk Debug\pulsegen.lkf:1 segment .text size overflow (915)
The command: "clnk -m Debug\pulsegen.map -lC:\Lib -o Debug\pulsegen.sm8 Debug\pulsegen.lkf " has failed, the returned value is: 1
exit code=1."
I think the code size is bigger then physical possible. Do I have to set certain compiler optimizations and how I can handle it?
Reply 3 years ago
Hi Andreas,
First of all it isn't clear to me whether you are using ST Visual Studio now or SDCC. I have no knowledge of SDCC so if you are using that, I can't help you.
It is unfortunate that Instructables do not allow me to upload binary and zip files, if they did I would have added my entire project folder. Now I did the second? best thing, I uploaded a zip file with the folder to google drive. You can download it via this link: (I hope)
https://drive.google.com/file/d/1CBsXv81967sk8cGdh6M8MaNBpM9e7wvX/view?usp=sharing
The second thing I uploaded to google drive is a zip file with what I call my "template" unzip it into its own folder and read the "__SETTING UP STVD FOR STM8.txt" file. It tells you how I setup every STM8 project. I don't know if it is the correct way of doing things, but it works for me. Remenber that I don't use most of the files of the Standard Peripheral library. As it didn't fit in the flash of the STM8.
https://drive.google.com/file/d/1FQtZoaEBDwPYDkDil58tJ0X2rRrJI6Pa/view?usp=sharing
The answer to your last question, the optimization is yes. Do not use "USE_FULL_ASSERT" and take care to add the "+split" option to the compiler.
Good luck!
Wilko
Reply 3 years ago
Hi Wilko,
thanks for your feedback. I appreciate your work. Yes, I'm using ST visual develop together with COSMIC compiler. The first link has not worked for me. However the second one with the template.zip did work. I will check out the setting instructions you wrote. I will give a short notice if I have success.
Update: Meanwhile I could figure out the issue. It was the file stm8_interrupt_vector.c, which caused the issues. I have used that one you provided in your template. Furthermore the location of this file is important. It has to be in root design not in an subfolder e.g. containing other source files.
In additon to your hints I have completely rejected all header files to the SPL within the stm8s_conf.h, because all needed files to cope with the peripherals are provided by you (with prefix stm8_ instead of stm8s_ for the SPL files).
Still, I have some problems to understand the map file in order to figure out the memory usage.
Nevertheless, thanks for your support.
Regards Andreas
Reply 3 years ago
And this one?
https://drive.google.com/file/d/1pSWbCL407WGQmuZmJy3ABFnOuvoNV1U5/view?usp=sharing
Reply 3 years ago
Hello Wilko,
sorry for delayed answer. Yes, this works. Thanks! Last question (at this time) regarding the correct TFT display. I have seen a lot of the displays but with slightly differences. Do I have to consider something (e.g. the chip with a dedicated suffix like R or S etc.)?
Best regards,
Andreas
Reply 3 years ago
As far as I remember it has the letter s at the end. I have two types of the TFT screen, one has 8 pins on one side, the other has 11. Funny thing is that the one with 8 pins has a pin for the LED (backlight) and the one with 11 pins does not. They both work, I have seen no differences in functionality.
Good luck,
Wilko
Reply 2 years ago
Hello Wilko, after getting the display - everything works. For me the display refresh was quite slow, still ok as you mentioned. I have checked out a Software-SPI implementation written in assembler:
https://hw-by-design.blogspot.com/2018/07/stm8-sof...
The Display clear has significantly speed up. The SPI clock is inrcreased from ca. 634kHz to 1.78MHz. In addition to performance improvement the memory size has been reduced by some bytes. Two files have been updated the st7735.c and the corresponding header file (some defines). The article provides the code itself and some background informations as well.
Regards Andreas
Reply 2 years ago
Very interesting. I haven't done anything in assembler since I made an eprom programmer on my BBC-model-B home computer, that must be almost 40 years ago by now. I'll be busy reading the articles on your site and perhaps give assembler another try.
Reply 2 years ago
Don't worry, I have also no experience in assembler. However if you have small well encapsulated functions as "SPI write", which give a performance boost, then at least I give a try. From my perspective the code is also well documented on the web site and tested as well. So, the change is not really neccessary but particular improvement.
Question 3 years ago
HEXファイルは有りませんか?
Answer 3 years ago
電子工作で作りたいのですが、HEXファイルはありませんか?
Reply 3 years ago
Yes, I have the binary, unfortunately Instructables does not allow me to upload it. But I can send it to your email address (if you tell me what that is :-))
The file is called pulse_generator.s19 but to me it looks like a standard hex.file. You can upload it with ST visual Programmer an a (clone) ST-Link
はい、私はバイナリを持っていますが、残念ながらInstructablesではアップロードできません。 しかし、私はあなたのメールアドレスにそれを送ることができます(あなたがそれが何であるかを私に教えてくれれば:-))
このファイルはpulse_generator.s19と呼ばれますが、私には標準のhex.fileのように見えます。 STビジュアルプログラマーa(クローン)ST-Linkでアップロードできます。
Reply 3 years ago
Beter late than never :-)
Here you can download it:
https://drive.google.com/file/d/1CZa8HAPlmv7hwOyM7...