Saturday, December 19, 2020

Pinout of nRF51822 board HW-651

 I recently bought a nRF51822 board to build a Bluetooth mouse for disabled people. The idea is that if I can get a Bluetooth HID service going on an nRF chip, then I can add various sensors to it and use it for their various needs. It can be a mouse, it can be a key or other things. I opted for the nRF family because they support open source applications and therefore a non-bloated development environment can be produced.

The board that I bought for the purpose is this:

nRF51822 board marked "HW-651". Note nothched corner.

Well, there is a problem. No datasheet came with it nor can be found anywhere on the Internet. The pinout is completely unknown. It is also different from the pinouts of similar boards. So this post is to provide the information that I have found so far. 

There are two pin headers marked "J1" and "J2", each with 2x9 pins. J1 is under the WiFi symbol, and J2 is under the marking "HW-651". Pin 1 has a square pad, and for J1 it is just next to the WiFi symbol and towards the outer edge of the board, and for J2 it is next to the "HW-651" marking, towards the center of the board. The pin numbers proceed in a zig-zag pattern as generally done in pin headers. BTW, the pin header is small and not the .1' type. It appears to be metric with 2mm spacing.

I traced the pinouts from the datasheet and a magnifying glass. The power supply pins and some others, were tested under the microscope with a continuity tester. A few of the pins are yet unidentified. They are marked "?" in the table.


This is J1 pinout:


Func Pin Hdr IC Pin IC Pin Pin Hdr Func
P0.21
1 40
13,33,342GND
P0.23
3 42
41
4 P0.22
P0.25 5 44 43 6 P0.24
? 7 ? ? 8 ?
P0.29 9 48 47 10 P0.28
P0.30 11 3 1,35,36 12 Vdd
P0.00 13 4 13,33,34 14 GND
P0.02 15 6 5 16 P0.01
P0.04 17 8 7 18 P0.03

This is J2 pinout:


Func Pin Hdr IC Pin IC Pin Pin Hdr Func
P0.20 1 28 272P0.19
P0.18 3 26 25 4 P0.17
SWCLK 5 24 23 6 SWDIO
P0.16 7 22 21 8 P015
P0.14 9 20 19 10 P0.13
P0.12 11 18 17 12 P0.11
P0.10 13 16 15 14 P0.09
P0.08 15 14 11 16 P0.07
P0.06 17 10 9 18 P0.05

I will determine the missing pins under a microscope with continuity tester when I have more time. I have checked the pins VDD, GND, SWCLK, SWDIO to be correct (I got a reply from the chip with the progrmmer.)

Sunday, September 27, 2020

Asus T200 Transformer Does not Power Up

 

A friend handed over a "dead" Asus T200 Transformer tablet/notebook recently. He said: "It is broken. Use its components if you want and throw away the remains!" It looked in good condition, so I checked the Internet about possible remedies. Many tablets can be restarted from power off or sleep state by using the "Power" and "Volume Up or Down" button press. I tried it, and hey, the computer powered up. So easy (but I would not be writing it if it were so easy; read on!).

Asus T200 - many have power up problems.


 

 Anyway, after the usual bragging about how sensitive my hands were etc, and saving his files, after a few days, surely it started to power up with more and more difficulty! Rituals of power button combinations, adapter connected/disconnected and all sorts of stuff. Eventually it was dead as a stone. As a last resort, I opened it up, disconnected the battery for a while and re-connected. However it did not help. Dead as a stone...

 Then I started looking around the circuitry with a magnifying glass. After a while I noticed a telltale sign of corrosion around the connector of the power daughter PCB (the power and volume buttons are here). For those who are not familiar, corrosion looks like tiny green residue around gold plated contacts. Removing the connector, sure enough, there was corrosion in one line of the ribbon cable. The connector is outlined in the photo below. All that was required was to remove the cable by prying the black tab on the connector up, cleaning with alcohol, and re-inserting. The computer has been working fine since then.

Check for corrosion on the power daughterboard connector and cable.

There is much discussion about how to solve dead T200's through software, but this looks like a very real and repairable hardware fault. How to open your T200 etc. can be found on the internet and I wont repeat it here. Just remember to charge it before it can power up!

So, although the T200 is now an old computer, it is still capable of daily tasks like teleconference and web surfing. If you have one lying around, it may be salvaged.



Thursday, May 7, 2020

USB Data Acquisition on a $3 Board


It is possible to build a simple data acquisition system using the STM32F103 processor. A few analog ports are sampled at the frequency defined by the user and the result is sent over USB configured as a virtual com port, in human readable format to a computer. The code is available at my GitHub repo. This project demonstrates a few things:

  • Sample signals with a fairly precise sampling time,
  • Use ADC, DMA and a timer for analog to digiral conversion in hardware, completely independent from software,
  • How to use the CubeMX graphical configuration and code generation tool,
  • Create a USB CDC device (virtual com port, actually a virtual modem device),
  • Show what can be done using only a $3 microprocessor board.

 
Pots used to generate the voltage data.


The system uses a timer to trigger the ADC, which automatically samples one sequence of the channels you specify, stores them using DMA into your array. It finally sets a flag in an interrupt service routine (ISR).


At the same time, a USB CDC device is also set-up, so this appears to the computer as a virtual serial comm port (actually a modem device). At the firmware side, all that the user needs to do is to check a flag, called the ADC sequence complete flag, and if it is true, get the ADC results, format them into a string, and send the string to the host computer over the comm port. Since a standard USB device class is used, it should work on most modern OS'es without a driver.


The project also demonstrates how to use STM32 CubeMX to create a fairly complex system using the graphical user interface (GUI). The USB device setup can be simply created, as well as the timer, ADC, DMA, the links between them and the interrupts that they generate. After this, we only need to fill up a few locations, typically in main.c. Since STM32 CubeMX auto-generates the project sources, you must not interfere with its code. It must not interfere with what you write either. To deal with this, ST has come up with a method, where they seperated the generated code with this type of comments:

/* USER CODE BEGIN Includes */
  -> your code goes between these comments and survives CubeMX source re-generation.
/* USER CODE END Includes */
 -> whatever you write outside gets erased the next time CubeMX
re-generates your project source.

If you write between them, STM32 CubeMX does not touch your parts of the code even if you re-generate it using the GUI. Well, the method does make for bloated code. But at the end, if you are sure that you will not go through another re-generation iteration, you can delete all those markers and you will be fine and your code senile and readable (ask the OCD in me!).


The code is built as a GCC project using GNU make. The Makefile is modified a bit from the original, because the autogenerated one has too much repetition in file paths etc. I also added a "make jflash" rule so that you can use Texane's excellent st-util for ST-Link V2 programmer. To compile, you should have a GNU-ARM-Embedded toolchain installed (such as from the Launchpad site). Edit Makefile to point to your toolchain path. (BTW, I intentionally obfuscated the Makefile after it was first generated by CubeMX. In later re-generations of the source, CubeMX fails to recognize your Makefile for updating, does not modify it, and gives an error message. This is intentional. Just ignore that error).


After you program the processor, power it down and plug it back in through the USB port. It should appear as a CDC device (such as /dev/ttyACM0). Connect to it using a terminal emulator (I like kermit). After you set the port (no need to set connection speed in ACM devices) and connect, you should see the readings coming from ADC1, 2, 3 as:

...
200: ADC1=0, ADC2=4029, ADC3=4030
201: ADC1=1039, ADC2=4029, ADC3=4029
202: ADC1=2353, ADC2=4030, ADC3=4030
203: ADC1=3503, ADC2=4030, ADC3=4030
...
 
The first number is sample sequence. How you format the data is up to you. The above format is just for clarity; in a normal application a comma separated list is probably better. You can send data in binary form also. You can even build a simple data graph of sorts by using the excellent SerialPlot program. Give it a try!

There are a few things that can be changed:

  • Add or remove ADC channels -> See the ADC section in CubeMX
  • Change the sample and hold duration -> See the ADC section in CubeMX
  • Change sampling time -> See TIM3 section in CubeMX. The sampling time is calculated as follows: The processor runs at 72MHz. This is first divided in the prescaler (it is currently set to 7200), and further in the ARR (max count limit of timer/counter) which is set to 1000. The overall sampling time (Ts) is therefore 72,000,000  /7200 /1000  = 10 samples/s => One sample at every 0.1s, or, Ts=100ms. Or course, it can be much faster than this; this rate is only to make it easy fo visually follow the output.
You can modify them either in the source code or in CubeMX. If you modify directly in the source code, your changes will disappear the next time you re-generate the source from CubeMX. So it is better to do it from CubeMX.


Going through the source:


Here are some main points about the source code. The important thing to remember is that CubeMXis a tool for initializing the hardware. It does not generate a program that is useful for anything. We need to write that. Let's go over the significant parts. (Some of the parts mentioned below are already filled in by CubeMX):

It is important to start with the includes for the USB CDC functions. We add:

 #include "usbd_cdc_if.h"

This is the array for the string sent over the virtual comm port (we add):

   char msgbuf[128];

From this line onwards is the meat of the matter:


   /* USER CODE BEGIN 2 */
 
The following shows how to send a string over the virtual comm port:
 
  sprintf(msgbuf, "Starting device.\r\n");
  CDC_Transmit_FS((uint8_t *) msgbuf, strlen(msgbuf));


Starting the timer and the DMA are user's responsibility:

  HAL_TIM_Base_Start(&htim3); //AO!: Start the timer.

The ADC and DMA are initialized with the following HAL function. It specifies which ADC to use, which array the results of the conversion are stored and the length of the transfer. The actual link between the ADC and DMA was set in stm32f1xx_hal_msp.c:
 

  HAL_ADC_Start_DMA(&hadc1, adcBuf, ADC_BUFLEN); //Link DMA to ADC1

The infinite loop follows. It simply waits for the ADC_FLAG to become TRUE, which is set by the DMA transfer complete callback function. When this happens, the ADC results have already been stored in adcBuf[ ] array. We format  them as a string and send over the virtual comm port:

      sprintf(msgbuf, "%d,%d,%d,%d\r\n", i,
          (int)adcBuf[0], (int)adcBuf[1], (int)adcBuf[2]);
      CDC_Transmit_FS((uint8_t *) msgbuf, strlen(msgbuf));


CubeMX links ADC and TIM3 as:

static void MX_ADC1_Init(void){


The next significant part is the modification of the DMA transfer complete callback function, which sets ADC_FLAG. We add:


/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc1){
  ADC_FLAG=TRUE;
}


This is essentially all the important changes to main.c. There is one extra. I modified the error handler so that it sends the file and line number of where the error function was called, and then blocks there while flashing the LED at 20Hz. If the virtual comm port ever came up correctly by the time an error occurred in your program, you should see where the error was generated. This is done in _Error_Handler function.

The next significant file is stm32f1xx_hal_msp.c where the link between TIM3, ADC and DMA are set around the line /* ADC1 DMA Init */ (already done by CubeMX):

 __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);


So that's it. Please modify the code to suit your needs. "Share and Enjoy!"


PS: If you are new to STM32 programming in GCC, please see my blog on how to set up a development environment for it.

Wednesday, May 6, 2020

Flysky i6 - Suddenly broke down!


My previous blog was about building a flight sim controller using a regular Flysky i6 transmitter. It worked nicely for some time, until one day, the screen suddenly went blank, and it was completely dead...

I checked the regular suspects. The voltage regulators for the processor and the RF module worked fine and everything looked OK. It just would not turn on at all. I was thinking that probably I shorted the 5V coming from the USB port to the 3V3 line on the board for an instant and burned some of the main chips. I gave up on the device and started to build my custom joystic interface electronics and new firmware that read i6's potentiometers as the input to the joystick interface. I was planning to bypass all the electronics of the i6, and use only its sticks (analog voltages of the pots are nicely broken out at the top, just below "CON8").

The day to start wiring it up to the joystick interface. I switched the i6 on out of usual habit. It turned on normally and worked! WTF! Alright, it means intermittent connections. However, it worked only for a short time and went bad again. Such intermittent operation continued from then on. Usual suspect in this case, is bad connections (solder joints, connectors, wires etc.). Looking at the board under a makeshift microscope, the EEPROM chip seemed to have suspicious soldering. I resoldered it. No difference. Then I noticed the top cover of the crystal was bent inwards, as if struck by something. I did not have a 4 pin 5032 SMD package 8MHz crystal laying around, so I unsoldered it and replaced it with a through hole regular crystal. It did work a bit better but still went bad from time to time. When it did work, pressing on the processor package would kill it. But looking at the processor solering, all looked fine.

Eventually, I laid my eyes on the large RF module. I had not given it much thought until then, because in regular RC transmitters, the RF module simply receives input signals from the rest of the circuit. Even if it is broken, the rest of the system would still work. But this one is different; it has bi-directional communication with the processor for setup and running. If it does not cooperate, the processor cannot go further in the program. It had one of the worst soldering job that I have seen on Chinese products (too large for the pick and place machine, it was probably hand soldered). Fairly wide pin spacing, so it was easy to re-solder it. Well, that was the problem. After the re-soldering, it has worked flawlessly since.

The bad solder joints on the RF module...
In post-mortem, I understood why it broke. I had removed the PCB from the transmitter to check some of the soldering side traces to the trainer port. However, the PCB is held very tightly in plastic standoffs and requires much  force to get it out. The RF module is fairly large and there is no strain relief betwen it and the PCB. So all three conditions must have come together and probably caused some solder connections or their PCB traces to crack. The processor then received no reply from the RF module at startup, and the whole system simply hang.

Now, some rant. There is obviously a reason why these units are so cheap compared to brand name products. It is inevitable that factory reject parts or shady components find their way into them. The plastic or wiring is not the best quality. In this case, the crystal was obviously of dubious quality. I still think that Flysky i6 is exceptionally well made, and if you don't fiddle with it, it will give years of good service. I also think that it is a great platform to tinker with, especially if you do  not use it for a critical system, such as a flight simulator.

So there it is. If your Flysky i6 breaks down and goes completely blank at some point, the RF module re-solder is worthy of a repair candidate.

Saturday, April 4, 2020

Fly Sky i6 as a flight simulator TX

These days I cannot fly physical RC, so I thought about flying with a simulator. There are very nice simulator software out there. One of the best is Picasim. It has great flight dynamics and graphics. More importantly for me, it can easily run under Linux using wine (give it a try!). Next is the transmitter box. While it is possible to buy a transmitter box for flight sims, they are generally not good quality or offer much configuration.

Looking around the web, I found Fly Sky i6, an entry level transmitter which offers a great number of options. Better still, it is available from China without a receiver or any other accessories, for about $35 including shipment. This is about the same price as "dumb simulator transmitters". The i6 is very interestig because it has a great followers group who are modifying its firmware to make it better. I would have hesitated to use it for a real flying RC model, but when I opened it up for the first time, I was surprised with the good quality. It can be trusted upon to fly simple real RC models.

Back to the topic. One of the most interesting features of the i6 is its "trainer port". It is documented well. The trainer port is a "female mini DIN 4" shape, A.K.A. "S video", and has 5 connections available: GND, PPM OUT, PPM IN, RX, TX. A matching mini DIN 4 male connector looks like this FYI:
Mini DIN 4 male connector (A.K.A. S video)
Mini DIN 4 male connector (A.K.A. S video)

If you build (see below) or buy a PPM to USB joystick converter module (for around $11), you can immediately use the transmitter straight away. However, there are two "problems" with that! First of all, where is the fun of building if you are simply buying stuff and plugging them together? Secondly, of you buy a sim module, then you will need to run the transmitter on batteries which you must remember to buy replacements or to charge. The purpose of this post is to describe the modifications to the i6 electronics so that you can power it directly from your USB port.

Two modifications are required to the i6:
  1. Modify the trainer port pins so that one of them becomes a power input pin,
  2. Reduce the power consumption so that it will run from the 100mA available from the USB port.

Modification of the tranier port functions

You need to eliminate one of the functions on the trainer port so that you can make room for the power input pin. I selected to eliminate the "PPM IN" pin (of course another can be selected). In essence, all that you need to do is cut the wire to the PPM IN pin from the connector and solder it to the power switch and you would be done for this step.
However, there is a smarter way! The trainer port is connected to the rear case with a cable and a nice connector. It is possible to make a few changes to the components so that the cable is intact. The changes are shown below:
Changes to the PPM IN connection to make it a power supply pin.
I labeled the changes in the photo to match the item numbers below.
  1.  The PPM IN signal goes to connector J8, second pin from top. (The top pin is GND by the way, and the third from top is PPM OUT pin.)
  2. There is a reverse biased protection diode D1 that shorts the PPM IN to 3V3 supply on the board if it exceeds the allowable voltage. Remove that diode. Connect a wire to its left pad (next to the buzzer). This is a bit fiddly because of lack of room and you can see some burn marks on the buzzer. Well, it still "buzzes"...
  3. Connect the other end of the wire to the battery (+) pin. There is actually a more convenient pad for that labeled "BAT+" just to the left of the battery connector.
  4. If you find it difficult to solder to an SMD diode pad, there is also another easy location for the PPM IN pin here.
  5. Finally, you will need to remove the in-line protection resistor R13 that used to connect PPM IN to the processor. I generally lift up one pin and still leave the resistor connected, so that I can take back the changes in the future. In case you need, this is a 1k Ohm resistor.

That is all. Now the "PPM IN" pin on the trainer socket is connected to "battery in" circuit. CAUTION! This has two implications: The USB 5V is now connected directly to the battery box output. If you have batteries in the case, their voltage will be applied to the USB socket of your computer. Always remove batteries from the transmitter before using it. Second; because the USB supply voltage is only 5V, there is no room to use an in-line voltage protection. The i6 starts low voltage alarm when the supply voltage falls to around 4.5V. If you apply a high voltage to this pin (12V or similar) you will burn out the electronics. However, a simple reverse protection is installed, in the form of reverse biased diode D2 across the power supply. I find it good to have batteries installed for a balanced feel. So you may want to install only 3 of the 4 cells, or keep the power connector of the battery box unplugged, or cover one of the cells with tape etc. to keep them all installed. I selected the last option.

Reduction of power consumption

The second item is to reduce the power consumption. The transmitter draws about 100mA with the backlight on. The USB joystick module also requires about 20mA. The total of 120mA is above USB power supply specification and may cause a reset of the USB port on some PCs. The simplest way to reduce power is to remove power to the transmitter module of the i6. However, the microprocessor must communicate with it, and probably will not run without it. If there were a firmware upgrade that disabled the transmitter module, it would have been the best, but I did not come across one. The next biggest load of the i6 is the backlight. It consumes about 20mA and it is simple to disconnect. So that is what I did.
 
All you need to do is cut one trace so that the backlight is disabled. You can also remove R13 but i my case, it was firmly glued down. Anyway, if you want to re-enable the backlight, simply bridge the cut. BTW, the backlight is supplied directly from battery voltage.
 
Cut this trace to disable the backlight.

So, that is all. Now the i6 can be powered directly from USB, and the PPM IN pin has become a power input pin. If needed, it is still useable as a regular transmitter.

FWIW, my Fly Sky i6 PCB is labeled i6-M VER 2.4  20190623


The interface

Finally, the simulator interface (A.K.A the cable) itself. This converts the PPM (Pulse Position Modulation) signal which is standard across most RC transmitters, through the USB interface, into a joystick emulator for the PC. If you look around, there are many candidate projects. If you have an Arduino Leonardo or similar laying around, you can search for such projects. Since I am currently doing development with STM32 series microcontrollers, I had a blue pill board (STM32F103 breakout) laying around and chose to use that. Again, there are many good projects for it especially in Github. I found this one: "STM32-RC-USB-Adapter" by "voroshkov" and did not even bother re-compiling it; used the binary straight from the repo. It worked like a charm. It only supports 4 analog channels. I may in the future upgrade it to 6 all channels if I have time. I have modified the firmware to a contemporary version of MX Cube and added the 5th channel. I will put it up to GitHub later and put a link here. However, there is really no use for an extra analog channel in the simulator. 

The connections to the Blue Pill Board -> i6 are simple. Just buy a mini DIN 4 pin male plug and use that:
  1. GND -> GND
  2. 5V     -> The newly created Power input pin
  3. A8     -> PPM OUT
The photo is below:
The completed PPM to USB joystick adapter

Finally, you need an adjustment. Gamer joysticks have poor gimbals with a lot of electrical noise, so that most OS'es set the default dead zone at the center to a large value and you will not have precise flight control. In your OS remember to calibrate the joystick for good performance. Typically the OS will require that you move the sticks to extremes and then center them while it tries to detect the amount of noise. There is an easier way to calibrate an i6: Manually set each joystick minimum to '0', maximum to '1000', dead zone min and max both to '500' for the best performance.

The setup works well with my 2016 vintage Linux laptop running Ubuntu 16.04 as well as a contemporary Windows 10 machine. There is really no difference to the USB joystick definition across OS'es.

Have fun!

Wednesday, December 5, 2018

SOSS-S8: Simple Open Source Servo STM8S


SOSS-S8 is a DIY servo drive using feedback position control for DC motors with an incremental encoder. It is based on the low cost STM8S103F3 processor with all the functions handled properly by peripherals. Its main puropse is education, by allowing anyone to implement a closed loop position servo using very low cost components and completely open source software tools. You can learn many things with SOSS-S8:
  • Basic concepts in feedback control systems implementation (PID etc),
  • Convert continuous time designs into discrete time (digital control),
  • High precision fixed point math implementation in a tight control loop timing,
  • Writing code for real-time execution and using simple tools to check (LED, cheap logic analyser etc.),
  • Using sdcc, GNU make and standard peripheral library to write a software project for STM8S processor,
  • Simple digital filter implementation,
  • And others...

Currently a textbook PID algorithm is implemented. It is very satisfactory see the motor follow the reference signal, observe overshoot, steady state error, instabilities etc. or tune the control loop using textbook methods like Ziegler Nichols to achieve fast response and zero steady state error. But the real value comes when you connect the SOSS-S8 to your computer and see the control signals on your computer, in real-time, using the excellent SerialPlot software, which is also open source. The development was done in Linux, but it is perfectly valid for Windows or MacOS-X also.

It looks like this...
SOSS-S8 prototype. A PCB may follow someday...

Video here:


Specifications:

  • Control sampling frequency: 2kHz (can be an order of magnitude higher)
  • PWM frequency: 8kHz which is barely audible.
  • PWM resolution at 8kHz: 2000 counts
  • 16bit PID coefficients with 1/32 precision (scaling implemented for speed)
  • Hardware quadrature encoder reading suitable for high resolution encoders (500ppr, 1000ppr etc.)
  • 16 bit control parameters sent in real-time at 500 samples/second for graphing
  • Hardware PWM and timebase generation.
  • Generates its own time based step reference signals, or reads from a potentiometer using ADC.

Hardware:

SOSS-S8 is designed to use very low cost, off the shelf components:
Most of the components are already laying around in a typical workshop, but even if you buy them all, the maximum cost is less than $20. The circuit connections are very simple and can be done on a breadboard using the common "DuPont" patch cables in a few minutes.

I was too lazy to draw a proper schematic, especially since most people will probably be building it on a breadboard. So I drew a connection diagram instead.Next to each module there is a box with the names of connections of that module. Match the names of the connections in the box that is at the other end of the line.

Connection diagram. Match the same pin names at the same position at both ends of the lines.
(Sorry about the raster graphics -JPG- Blogspot does not allow vector graphics...)

How to install and run: Firmware

The firmware source code can be downloaded from my GitHub repository: SOSS-S8. The circuit connections can be found in the file motor.c as comments at the begining of the file. To build the firmware, you need to have sdcc installed as well as STM8S Standard Peripheral Library (one simple modification to the library is needed to make it sdcc compatible), a programmer software for the device programmer and  GNU make. I discuss how to install the development environment, in another blog post. You should test a few simple firmware projects to make sure they work before attempting this one. You can try the timebase project for example. About the only modification to the source is to modify both 'Makefile's, one in the main directory, and the other within 'libs' directory, to tell the compiler your path to the ST libraries. Windows users beware backslash '\' should replace forward slash '/' in the path values! Finally, all you should need to do is:
 $ make flash
and it should automatically compile first the files in the 'libs' folder, produce the local library file 'projectlib.lib', then continue with motor.c, and finally, if SOSS-S8 is connected to your computer, it will be uploaded and ran.

Remember that since this is a control system, the motor rotation direction is important. I took clockwise as viewed from the front of the motor as positive. That means if you apply a positive control signal to the motor, it should rotate in the positive direction, and the measured motor angle must be increasing, otherwise the control loop becomes positive feedback. If either of them are opposite, you should correct them: If the motor turns in the wrong direction, swap its terminals to the power amplifier, or if it turns in the right direction but the encoder reading is decreasing, swap the A and B leads of the encoder. If both are opposite, it will still work, but in the opposite direction to your reference commands. Of course you can also choose counterclockwise as the positive direction.

The communication settings

SOSS-S8 periodically sends measured values over the serial port. Currently it sends the measued motor angle, position reference and control signal at 500 samples/sec. This is quite fast even for the small and fast motor that I used in the prototype. To get the highest sample rate from the microprocessor at a given baud rate, we must minimize the number of bytes that are sent for each sample. The data is packed in the following way:
  • A constant synchronization sequence: 0xAA 0x55,
  • Parameter 1 formatted as 16 bit int, MSB first,
  • Parameter 2, 3, etc. in the same format,
  • No end delimiter or checksum.
For example, using 115200bps, to send 3 measurements, we need to send 8 bytes/sample. That means about 1400 samples/second can be sent, which is quite respectable! Roughly, it should be possible to send about 10 variables at 500 samples/sec before communication speed bottoms out. To add more measurements to the sent data, see the file motor.c. It is trivial; just type-cast the data to 16bit int, separate into two 8 bit u_int's and add them to the print buffer. The rest is taken care of automatically.

SOSS-S8 sends the data in binary form to minimize the processor overhead of formatting and to minimize the amount of data transferred over the serial connection. The serial data transfer is done in a non-blocking fashion: There is a print buffer, a buffer pointer, a message length variable and a "printing in progress" flag. A short code after the control routine periodically checks if the flag is set, and if the UART transmit buffer is empy, puts the next byte out. I did not bother writing a circular buffer for this because of the unnecessary computation overhead of pushing and popping data from the circular buffer. If you need, I have a sample circular buffer, let me know.

Going to the receiving end; we use the excellent program SerialPlot by hyOzd. The measurement data streaming from the serial port of SOSS-S8 in real-time is displayed on the PC. SerialPlot should be configured to synchronize to SOSS-S8 synchronization sequence, and accept int16 values in big endian format. This is configured in the "Data Format" tab of SerialPlot. Do the following settings:

  • "Custom Frame"
  • "Frame Start:" AA 55 (this is taken as hex without 0x prefix).
  • "# Channels:" 3
  • "Frame Size:" Fixed Size: 6 (use 2* # Channels)
  • "Number Type:" int16
  • "Endianness:" Big Endian
  • "Checksum:" unclick enabled.
That's it. Connect your USB-Serial converter, determine the port name (top dropdown list), click "Open" and you should see the motor parameters floating by, similar to an oscilloscope. For best results, adjust the sample size, graph size etc, from the "Plot" tab. I use 2000 samples for a good display. The graph here belongs to the motor shown in the top photo, with a reference that changes repeatedly every second, between 0 and 300 pulses.

You can take a snapshot of the data and save it as a csv file, to be plotted using your preferred graphing software (I like gnuplot, or perhaps Scilab, Octave, Matlab etc.). You can also pause the data, zoom into it and many other things.

Program structure:

The program is made up of the main part in motor.c, and the peripheral drivers in the individual files inside the libs folder. Here I will describe how the peripherals are used. I have first written the code using the functions provided by the STM8 Peripheral Libraries from the manufacturer. However, the code size quickly inflated. I designed the project in several parts (control, communication, timing etc.) and each part on its own quickly reached the ROM size limit. So I re-wrote the code by taking the individual register operations from the libraries and discarded everything else. So the code no longer depends on the library and the ROM size is now under 5kb. If you look at the source code, you will see the original library function calls commented out above their replacements.

Timer 1:

TIM1 is used as the hardware incremental encoder counter. It is initialized in the encoder mode. There is no interrupt associated with TIM1; it is read at control sampling times to calculate the control algorithm. The pins for A, B encoder inputs are PC6 and PC7. To be able to use PC6 and PC7 as TIM1 encoder inputs, it is necessary to program AFR0. To set it up you can write this into the configuration register:

 $ echo "00 00 ff 01 fe 00 ff 00 ff 00 ff" | xxd -r -p > TIM1CH1_Options.bin
 $ stm8flash -c stlinkv2 -p stm8s103f3 -s opt -w TIM1CH1_Options.bin
The associated files are: tim1.c and tim1.h.

Timer 2:

TIM2 is used as the hardware PWM generator for the power amplifier. I used it in asimple way where either TIM2 CH1 or CH2 generate the PWM waveform and the other is kept at ground level. After the calculation of the control signal, the PWM compare registers are adjusted. The PWM frequency is a compromise between frequency and resolution. We want high frequency so that it is not audible but at the same time we want the PWM count limit to be high for high control signal resolution. However their product equals the processor clock frequency. As a reasonable compromise, I selected 8kHz PWM frequency and 2000 count limit. The pins for PWM output are TIM2 CH1: PC5 and CH2: PD3. Again, PC5 can only be used as TIM2 output after the same modification of AFR0 explained above (no need to do anything else). The associated files are: tim2.c and tim2.h.

Timer 4:

TIM4 is used as a timebase interrupt generator. It is a simple 8 bit counter with an inflexible prescaler that can be set only to powers of 2 to divide the processor main clock. I set the prescaler to 1/32 and the count limit to 250 to obtain 2kHz INT. There is a further counter which raises a global flag at every millisecond to generate a ms event for easy timing. The MS event is counted in main() for several jobs. The associated files are: tim4_tbase.c and tbase.h.

ADC 1 CH2:

ADC is used as a reference measurement. A potentiometer can be conected here as a voltage divider. One minor simplification is that a new reading is initiated at the end of control signal calculation rather than at the begining. This is to reduce overhead before control calculation. We use ADC1 CH2 (shared with PC4).

main() triggers the ADC conversion. At EOC (End Of Conversion) an INT is produced. The conversion value is stored in the global variable ADC_RES to be used at the earliest convenience... Conversion is right aligned.

UART:

UART1 transmits variables to a PC. It is initialized to 115200Baud. Some simple string send functions are provided. At the moment, only transmit is used, but command receive methods will be later implemented.

ETC:

This rounds up the more significant peripherals. Of course there is more to it; the clock configuration, GPIO configuration and 7 segment LCD interfaces:

The files clock.c and clock.h manage the clock settings. HSI (High Speed Internal) clock is used and the processor clocked ad 16MHz. It also switches on the peripherals which are needed. The other peripherals are left powered off. Please check clock.c if you want to add more peripherals.

The files gpio.c and gpio.h are used to initialize the GPIO ports (LEDs etc.). Fairly straightforward.

The files HT1621_NoLib.c and HT1621_NoLib.h are for driving the LCD display. I think I had ported them from Arduino library to STM32, and then to STM8 here. The source was further modified to remove dependency on the STM Libraries.

Main function:

Let me explain a little about the main() last but not least. It is in motor.c. It starts typically with the peripheral and variable initializations. Especially the control variables structure ControlValues_t  and EncoderValues_t are initialized. Here the control gains are also set. I wanted a simple way of assigning square wave periodic reference. it is stored in the array references[]

Particularly, the define "ANALOG_REFERENCE" drives the control from ADC. If this is commented out, square wave reference is produced.

The main loop has the following functions:

Check the MS event flag if(MS_TickEventFlag==TRUE) and increment various counters. This makes it simple to generate timed events. For example, the reference value is updated every second.

If the flag for control signal reference is set, the motor encoder is read and motor position is displayed on the LCD.  If analog reference is asserted, first a low pass filter is used to smooth out the voltage reading (most pots are noisy!). The filter is implemented using int manipulation; we shift left, do the arithmetic and shift right 3 times which gives 1/8 precision. Then error calculation is done and control function is called. Finally the next ADC reading is triggered.

If the reference switch time has come if(REF_count>=REF_PERIOD), the next reference value is selected from the array references[].

If the time has come to transmit the next control values to the PC (I call that print): if(PRINT_count>=PRINT_PERIOD), the print buffer is populated. First the synchronization byets 0xAA and 0x55, then the variables are written to the buffer. I typecast the values to int16_t, and then write them to the buffer in big endian format. The printing state variable is also set to TRUE so that the non blocking print routine can send the characters.

Finally the non blocking print routine sends the characters one by one as soon as the UART transmit buffer is empty.

Finaly, the apply_control() function. It calculates the control algorithm and applies it to TIM2 for PWM generation. A simple anti-windup is also performed here. The time spent in the control algorithm can be used to check the processor utilizatin using an oscilloscope. I am hoping to find time to implement other control algorithms for demonstration in this function.


So, go ahead and build yourself a SOSS-S8 and try your hand at closed loop control implementation!

Saturday, September 22, 2018

STM8S time base using SDCC

The standart "hello world" program is usually blinking LEDs. The straightforward way to do this is to use a loop which wastes CPU time. This is simple but imprecise! What to do if we want to blink a LED at, say, 1Hz?

The solution is to use a hardware timer. We set a hardware timer to count from the system clock. If we know the clock rate, then we can calculate the number of counts required. When the timer overflows, we know the predefined time has passed.

We can either keep checking and wait for the timer to overflow, or set an interrupt service routine (ISR) so that it notifies the program of the overflow. Here I demonstrate the latter. A blocking timer function "Delayms" is given. Its argument is the number of milliseconds that the function will block. Non blocking timing is also demonstrated. It uses the signal set by TIM1 overflow ISR.

The above is general knowledge, but my intention here is to show you the details of how the timer (TIM1) is set up for overflow interrupts, how the blocking and non blocking delay can be implemented and how external variables are declared and used (a variable that is set and used by functions in different files).

I also show how multiple-file projects can be implemented in SDCC, with several Makefiles. This project is made up of several files. Some reside in a local directory "libs', others reside in the STM8 provided peripheral libraries. All of the "other" files are first compiled into respective object files (.rel extension), and then stored in a local library built specifically for this project. In this project, the library file is "libs/projectlib.lib". The Makefiles check if "projectlib.lib" is up-to-date and if not, also generate it.

For the source code, see my Github site:
https://github.com/ahmetonat/STM8S-time-base-using-SDCC

To build and flash, type:
$ make flash

For an introduction to STM8 programming in SDCC, see another post of mine.

After building and flashing, you should see the LED blink rapidly for 10 pulses first; this uses the blocking delay function. Then it should settle into a double blink pattern, using the non-blocking metod.