Read datasheets 2: SPI on STM32; PWM, timers and interrupts on STM8
В the first part I tried to tell hobby electronics engineers who grew out of Arduino pants how and why they should read datasheets and other documentation for microcontrollers. The text turned out to be large, so I promised to show practical examples in a separate article. Well, he called himself a loader ...
Today I will show you how to use datasheets to solve fairly simple, but necessary tasks for many projects on STM32 (Blue Pill) and STM8 controllers. All demo projects are dedicated to my favorite LEDs, we will light them in large quantities, for which we will have to use all sorts of interesting peripherals.
The text again turned out to be huge, so for convenience I’m making the content:
Disclaimer: I am not an engineer, I do not pretend to have deep knowledge in electronics, the article is intended for amateurs like me. In fact, I considered myself two years ago as the target audience. If someone had told me then that it’s not scary to read datasheets for an unfamiliar chip, I would not have spent a lot of time looking for some pieces of code on the Internet and inventing crutches with scissors and adhesive plaster.
The focus of this article is datasheets, not drafts, so the code may not be too polished and often a crutch. The projects themselves are very simple, although they are suitable for the first acquaintance with a new chip.
I hope that my article will help someone at a similar stage of diving into a hobby.
STM32
16 LEDs with DM634 and SPI
Small project using Blue Pill (STM32F103C8T6) and DM634 LED driver. With the help of datasheets, we will deal with the driver, STM IO ports and configure SPI.
DM634
Taiwanese chip with 16 x 16-bit PWM outputs, can be chained. The younger 12-bit model is known from a domestic project light pack. At one time, choosing between DM63x and the well-known TLC5940, I settled on DM for several reasons: 1) TLC on Aliexpress is definitely fake, but this one is not; 2) DM has an autonomous PWM with its own frequency generator; 3) it could be bought inexpensively in Moscow, and not wait for a parcel from Ali. And, of course, it was interesting to learn how to control the chip yourself, and not use a ready-made library. Chips are now mainly presented in the SSOP24 package, they are easy to solder on the adapter.
Since the manufacturer is Taiwanese, datasheet to the chip is written in Chinese English, which means it will be fun. First look at the pinoutPin connection) to understand which leg to connect what, and a description of the pins (Pin Description). 16 pins:
Sink DC sources (open drain)
Zinc / open-drain output - stock; source of incoming current; an output connected to ground in an active state - LEDs are connected to the driver by cathodes. Electrically, this is, of course, no "open drain" (open drain), but in datasheets such a designation for outputs in drain mode is often found.
External resistors between REXT and GND to set the output current value
A reference resistor is installed between the REXT pin and ground, which controls the internal resistance of the outputs, see the graph on page 9 of the datasheet. In the DM634, this resistance can also be controlled by software by setting the overall brightness (global brightness); I won’t go into details in this article, I’ll just put a 2.2 - 3 kOhm resistor here.
To understand how to control the chip, let's look at the description of the device interface:
Yeah, here it is, Chinese English in all its glory. It is problematic to translate this, you can understand it if you wish, but there is another way - to look at how the datasheet describes the connection to the functionally close TLC5940:
… Only three pins are required to enter data into the device. The rising edge of the SCLK signal shifts the data from the SIN pin to the internal register. After all data has been loaded, a short high XLAT signal latches the serially transferred data in internal registers. Internal registers are gates triggered by the XLAT signal level. All data is transmitted MSB first.
Latch - latch / latch / latch. rising edge is the leading edge of the pulse MSB first – the most significant (leftmost) bit forward. to clock data – transmit data sequentially (bit by bit).
Word latch often found in the documentation for chips and is translated in a variety of ways, so for understanding I will allow myself
small educational programThe LED driver is essentially a shift register. "Shift" (shift) in the name - bit-by-bit movement of data inside the device: each new bit shoved inside pushes the entire chain forward in front of it. Since no one wants to observe the chaotic blinking of LEDs during the shift, the process takes place in buffer registers separated from the workers by a shutter (latch) is a kind of dressing room where the bits line up in the desired sequence. When everything is ready, the shutter opens and the bits go to work, replacing the previous batch. Word latch in the documentation for microcircuits almost always implies such a damper, in whatever combinations it is used.
So, data transfer to DM634 is carried out as follows: set the DAI input to the value of the high bit of the far LED, pull DCK up and down; set the DAI input to the value of the next bit, pull DCK; and so on until all bits have been transmitted (clocked in), after which we pull LAT. This can be done manuallybit bang), but it’s better to use the specially sharpened SPI interface for this, since it is presented on our STM32 in two copies.
Blue Tablet STM32F103
Introductory: STM32 controllers are much more complicated than Atmega328 than they can be scary. At the same time, for reasons of energy saving, almost all peripherals are disabled at the start, and the clock frequency is 8 MHz from an internal source. Fortunately, the STM programmers wrote a code that brings the chip to the “calculated” 72 MHz, and the authors of all the IDEs I know included it in the initialization procedure, so we don’t need to clock (but you can if you really want). But you have to turn on the peripherals.
Documentation: The popular STM32F103C8T6 chip is installed on Blue Pill, there are two useful documents for it:
Data Sheet for microcontrollers STM32F103x8 and STM32F103xB;
Pinouts - chip pinouts - in case we decide to make boards ourselves;
Memory Map - a memory map for a specific chip. The Reference Manual has a map for the entire line, it mentions registers that are not on ours.
Pin Definitions table - listing the main and alternative pin functions; for the “blue pill” on the Internet, you can find more convenient pictures with a list of pins and their functions. Therefore, we immediately google Blue Pill pinout and keep this picture at hand:
NB: there was an error in the picture from the Internet, noticed in the comments, for which thanks. The picture has been replaced, but this is a lesson - it is better to check information not from datasheets.
We remove the datasheet, open the Reference Manual, from now on we use only it.
Procedure: deal with standard input / output, configure SPI, turn on the necessary peripherals.
Input Output
On the Atmega328, I/O is extremely simple, which is why the abundance of STM32 options can be confusing. Now we only need conclusions, but even there are four options:
Open Drain Output, Push-Pull Output, Push-Pull Alternate, Open-Drain Alternate
"Pull-push" (push pull) - the usual output from the Arduino, the pin can be either HIGH or LOW. But with the "open drain" arise difficulties, although in fact everything is simple here:
Output configuration / when port is assigned to output: / output buffer enabled: / – open drain mode: "0" in the output register enables N-MOS, "1" in the output register leaves the port in Hi-Z mode (P-MOS is not activated ) / - push-pull mode: "0" in the output register activates N-MOS, "1" in the output register activates P-MOS.
All open drain difference (open drain) from "push-pull" (push pull) is that in the first pin it cannot take the HIGH state: when a unit is written to the output register, it goes into high resistance mode (high impedance, Hi-Z). When writing zero, the pin in both modes behaves the same, both logically and electrically.
In normal output mode, the pin simply translates the contents of the output register. In "alternative" it is controlled by the corresponding peripheral (see 9.1.4):
If the port bit is configured as an alternate function output, the output register is disabled and the pin is connected to the output signal of the peripheral.
The alternative functionality of each pin is described in Pin Definitions The datasheet is on the downloaded picture. When asked what to do if the pin has several alternative functions, the answer is given by a footnote in the datasheet:
If multiple peripherals use the same pin, to avoid conflict between alternative functions, only one peripheral should be used at a time, switching using the Peripheral Clock Enable bit (in the corresponding RCC register).
Finally, pins in output mode also have a clock speed. This is another energy saving feature, in our case we just set it to the maximum and forget it.
So: we are using SPI, which means that two pins (with data and with a clock signal) should be “alternative push-pull function”, and one more (LAT) should be “normal push-pull”. But before assigning them, let's deal with SPI.
SPI
Another little hack
SPI or Serial Peripheral Interface (serial peripheral interface) is a simple and very effective interface for communicating MK with other MKs and the outside world in general. The principle of its operation has already been described above, where about the Chinese LED driver (see section 25 in the reference manual). SPI can operate in master ("master") and slave ("slave") modes. SPI has four basic channels, of which not all may be involved:
MOSI, Master Output / Slave Input: this pin sends data in master mode, and receives data in slave mode;
MISO, Master Input / Slave Output: on the contrary, in the master it receives, in the slave it gives;
SCK, Serial Clock: sets the frequency of data transmission in the master or receives a clock signal in the slave. Essentially, beats the beats;
SS, Slave Select: with this channel, the slave knows that they want something from him. On STM32 it is called NSS, where N = negative, i.e. the controller becomes a slave if this channel has a ground. It combos well with the Open Drain Output mode, but that's another story.
Like everything else, SPI on STM32 is rich in functionality, which makes it somewhat difficult to understand. For example, it can work not only with SPI, but also with an I2S interface, and their descriptions are mixed in the documentation, you need to cut off the excess in a timely manner. Our task is extremely simple: you just need to give data using only MOSI and SCK. We go to section 25.3.4 (half-duplex communication, half-duplex communication), where we find 1 clock and 1 unidirectional data wire (1 clock and 1 unidirectional data stream):
In this mode, the application uses SPI in either transmit-only or receive-only mode. / The transmit-only mode is similar to duplex mode: data is transmitted on the transmit pin (MOSI in master mode or MISO in slave mode), while the receive pin (MISO or MOSI respectively) can be used as a normal I/O pin. In this case, it is enough for the application to ignore the Rx buffer (if it is read, there will be no transmitted data).
Great, the MISO pin is free, let's connect the LAT signal to it. Let's deal with Slave Select, which can be controlled programmatically on STM32, which is extremely convenient. We read the paragraph of the same name in section 25.3.1 SPI General Description:
NSS software control (SSM = 1) / Slave selection information is contained in the SSI bit of the SPI_CR1 register. The external NSS pin is left free for other application needs.
It's time to write to the registers. I decided to use SPI2, we are looking for its base address in the datasheet - in section 3.3 Memory Map (Memory Map):
We open section 25.3.3 with the telling title "Configuring SPI in master mode":
1. Set the serial interface clock with the BR[2:0] bits in the SPI_CR1 register.
The registers are collected in the reference manual section of the same name. Address shift (address offset) CR1 has 0x00, by default all bits are cleared (reset value 0x0000):
The BR bits set the controller clock divider, thus determining the frequency at which the SPI will operate. The STM32 frequency will be 72 MHz, the LED driver, according to its datasheet, operates at a frequency of up to 25 MHz, so we need to divide by four (BR[2:0] = 001).
2. Set the CPOL and CPHA bits to define the relationship between data transfer and serial interface clock (see diagram on page 240)
Since we are reading a datasheet here, and not looking at schematics, let's take a closer look at the textual description of the CPOL and CPHA bits on page 704 (SPI General Description):
Clock phase and polarity
Using the CPOL and CPHA bits of the SPI_CR1 register, you can programmatically select four options for timing ratios. The CPOL (Clock Polarity) bit controls the state of the clock signal when no data is being transmitted. This bit controls master and slave modes. If CPOL is reset, the SCK pin is low at rest. If the CPOL bit is set, the SCK pin is high when idle.
If the CPHA (Clock Phase) bit is set, the MSB trap strobe is the second edge of the SCK signal (falling if CPOL is cleared, or rising edge if CPOL is set). The data is latched on the second clock change. If the CPHA bit is cleared, the rising edge of the SCK signal (falling edge if CPOL is set, or rising edge if CPOL is clear) serves as the high bit trap strobe. Data is latched on the first clock change.
Having tasted this knowledge, we come to the conclusion that both bits must remain zero, because we want the SCK signal to remain low when not in use, and data to be transmitted on the rising edge of the pulse (see fig. rising edge in datasheet DM634).
By the way, here we first encountered a feature of the vocabulary in the ST datasheets: in them the phrase “reset the bit to zero” is written to reset a bitAnd not to clear a bit, like, for example, Atmega.
3. Set the DFF bit to determine the 8-bit or 16-bit data block format
I specifically took a 16-bit DM634 so as not to bother with the transfer of 12-bit PWM data, like the DM633. DFF makes sense to put in unity:
4. Configure the LSBFIRST bit in the SPI_CR1 register to define the block format
LSBFIRST, as its name implies, sets up the transmission with the least significant bit first. But the DM634 wants to receive data MSB first. Therefore, we leave it reset.
5. In hardware mode, if input from the NSS pin is required, drive the NSS pin high during the entire byte transfer sequence. In NSS program mode, set the SSM and SSI bits in the SPI_CR1 register. If the NSS pin is to be output, only the SSOE bit needs to be set.
Install SSM and SSI to forget about NSS hardware mode:
#define SSI 0x0100
#define SSM 0x0200
_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high
6. The MSTR and SPE bits must be set (they only remain set if the NSS is high)
Actually, with these bits we assign our SPI as a master and turn it on:
SPI is configured, let's immediately write functions that send bytes to the driver. Continue reading 25.3.3 "Configuring SPI in Master Mode":
Data transfer procedure
The transfer begins when a byte is written to the Tx buffer.
The data byte is loaded into the shift register at parallel mode (from the internal bus) during the transmission of the first bit, after which it is transmitted in consistent MOSI pin mode, first or last bit forward depending on the setting of the LSBFIRST bit in the CPI_CR1 register. TXE flag is set after data transmission from Tx buffer to shift register, and an interrupt is generated if the TXEIE bit in the CPI_CR1 register is set.
I highlighted a few words in the translation to draw attention to one feature of the implementation of SPI in STM controllers. On the Atmega, the TXE flag (Tx Empty, Tx is empty and ready to receive data) is set only after the entire byte has been sent out. And here this flag is set after the byte has been shoved into the internal shift register. Since it is shoved there with all the bits at the same time (in parallel), and then the data is transmitted sequentially, TXE is set before the byte is completely sent. This is important because in the case of our LED driver, we need to pull the LAT pin after sending all data, i.e. only the TXE flag will not be enough for us.
Which means we need another flag. Let's look at 25.3.7 - "Status Flags":
<...>
BUSY flag
The BSY flag is set and cleared by hardware (writing to it has no effect). The BSY flag indicates the state of the SPI communication layer.
It resets:
when the transfer is completed (except in master mode if the transfer is continuous)
when SPI is disabled
when a master mode error occurs (MODF=1)
If the transmission is not continuous, the BSY flag is cleared between each data transmission.
Okay, it'll come in handy. Find out where the Tx buffer is located. To do this, read the "SPI Data Register":
Bits 15:0 DR[15:0] Data Register
Data received or data to be transmitted.
The data register is divided into two buffers, one for writing (transmit buffer) and one for reading (receive buffer). A write to the data register writes to the Tx buffer, and a read from the data register will return the value contained in the Rx buffer.
Well, the status register, where there are TXE and BSY flags:
Well, since we need to transfer 16 times two bytes, according to the number of outputs of the LED driver, something like this:
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // finish transmission
LAT_pulse();
}
But we don't know how to pull the LAT pin yet, so let's go back to I/O.
Assign pins
In STM32F1, the registers responsible for the state of the pins are rather unusual. It is clear that there are more of them than Atmega, but they are also different from other STM chips. Section 9.1 GPIO General Description:
Each of the general purpose I/O ports (GPIO) has two 32-bit configuration registers (GPIOx_CRL and GPIOx_CRH), two 32-bit data registers (GPIOx_IDR and GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 16-bit reset register (GPIOx_BRR), and a 32-bit blocking register (GPIOx_LCKR).
Unusual, and also rather inconvenient, are the first two registers here, because the 16 pins of the port are scattered across them in a “four bits per brother” format. Those. pins XNUMX through XNUMX are in CRL, and the rest are in CRH. At the same time, the remaining registers successfully fit the bits of all the port pins - often remaining half "reserved".
For simplicity, let's start at the end of the list.
We do not need a blocking register.
The set and reset registers are quite funny in that they partially duplicate each other: you can write everything only in BSRR, where the upper 16 bits will reset the pin to zero, and the lower ones will be set to 1, or you can also use BRR, the lower 16 bits of which only reset the pin . I like the second option. These registers are important because they provide atomic access to the pins:
Atomic install or reset
You do not need to disable interrupts when programming GPIOx_ODR at the bit level: you can change one or more bits with a single APB2 atomic write operation. This is achieved by writing a "1" to the set/reset register (GPIOx_BSRR or, for reset only, GPIOx_BRR) of the bit to be changed. Other bits will remain unchanged.
Data registers have quite speaking names - IDR = Input Direction Register, input register; ODR= output Direction Register, output register. In the current project, we do not need them.
And finally, the control registers. Since we are interested in the pins of the second SPI, namely PB13, PB14 and PB15, we immediately look at CRH:
And we see that it will be necessary to write something in bits from the 20th to the 31st.
We already figured out what we want from the pins above, so here I will do without a screenshot, just say that MODE sets the direction (input if both bits are set to 0) and the speed of the pin (we need 50MHz, i.e. both pin to "1"), and CNF sets the mode: normal "push-pull" - 00, "alternative" - 10. By default, as we can see above, all pins have the third bit from the bottom (CNF0), it sets them to mode floating input.
Since I plan to do something else with this chip, for simplicity, I generally defined all possible MODE and CNF values for both the lower and upper control registers.
(LAT_low just by inertia, somehow it always was, let it stay for yourself)
Now everything is great, it just doesn't work. Because this is STM32, they save electricity here, which means that you need to turn on the clocking of the necessary peripherals.
Turn on clocking
The clock is responsible for clocking, they are also Clock. And we could already notice the abbreviation RCC. We are looking for it in the documentation: this is Reset and Clock Control (Management of reset and clocking).
As mentioned above, fortunately, the people from STM did the most difficult part of the clocking topic for us, for which many thanks to them (once again I will give a link to Di Halt's websiteto make it clear how confused it is). We only need registers responsible for enabling peripheral clocking (Peripheral Clock Enable Registers). First, let's find the base address of the RCC, it is at the very beginning of the "Memory Card":
And then either click on the link where to try to find something in the table, or, much better, go over the descriptions of the including registers from the sections about enable registers. Where do we find RCC_APB1ENR and RCC_APB2ENR:
And in them, respectively, bits that include the clocking of SPI2, IOPB (I / O Port B) and alternative functions (AFIO).
If there is an opportunity and desire to test, then we connect DM634 like this: DAI to PB15, DCK to PB13, LAT to PB14. We feed the driver from 5 volts, do not forget to combine the grounds.
STM8 PWM
PWM on STM8
When I was just planning this article, I decided, for example, to try to master some functionality of an unfamiliar chip with the help of only a datasheet, so that a shoemaker would not turn out without boots. STM8 was perfect for this role: firstly, I had a couple of Chinese boards with STM8S103, and secondly, it is not very popular, and therefore the temptation to read and find a solution on the Internet rests on the absence of these same solutions.
By default, STM8 operates at a frequency of 2 MHz, this must be corrected immediately.
HSI Clock (High Internal)
The HSI clock is derived from an internal 16 MHz RC oscillator with a programmable divider (1 to 8). It is set in the clock divider register (CLK_CKDIVR).
Note: HSI RC oscillator with a divider of 8 is selected as the master clock source at startup.
We find the address of the register in the datasheet, the description in refman and see that the register needs to be cleared:
Since we are going to run PWM and connect LEDs, let's look at the pinout:
The chip is small, many functions are suspended on the same pins. What is in square brackets is “alternative functionality”, it is switched by “option bytes” (option bytes) - something like Atmega fuses. You can change their values programmatically, but it is not necessary, because. The new functionality is activated only after a reboot. It's easier to use ST Visual Programmer (downloaded with Visual Develop), which can change these bytes. The pinout shows that the outputs CH1 and CH2 of the first timer are hidden in square brackets; it is necessary to set the AFR1 and AFR0 bits in STVP, and the second one will also transfer the output CH1 of the second timer from PD4 to PC5.
Thus, 6 pins will control the LEDs: PC6, PC7 and PC3 for the first timer, PC5, PD3 and PA3 for the second.
Setting up the I/O pins themselves on the STM8 is simpler and more logical than on the STM32:
Atmega-familiar data direction register DDR (Data Direction Register): 1 = output;
the first control register CR1, when output, sets the push-pull mode (1) or open drain (0); since I connect the LEDs to the chip with cathodes, I leave zeros here;
the second control register CR2 sets the clock speed when outputting: 1 = 10 MHz
PWM Frequency – frequency with which the timer ticks;
Auto-reload, AR – autoloaded value, up to which the timer will count (pulse period);
Update Event, UEV – an event that occurs when the timer has counted to AR;
PWM Duty Cycle - PWM duty cycle, often called "duty cycle";
Capture/Compare Value – value to capture/compare, counting up to which the timer will do something (in the case of PWM, it inverts the output signal);
preload value – preloaded value. compare value cannot change while the timer is ticking, otherwise the PWM cycle will break. Therefore, new transmitted values are placed in the buffer and pulled out when the timer reaches the end of the countdown and is reset;
Edge-aligned и Center-aligned modes – alignment on the border and in the center, the same as atmelovskie Fast PWM и Phase-correct PWM.
OCiREF, Output Compare Reference Signal - the reference output signal, in fact, what appears on the corresponding pin in PWM mode.
As is already clear from the pinout, two timers have PWM capabilities - the first and second. Both are 16-bit, the first one has a lot of additional features (in particular, it can count both up and down). We need both to work the same way, so I decided to start with the obviously poorer second one, so as not to accidentally use something that is not in it. Some problem is that the description of the PWM functionality of all timers in the reference manual is in the chapter about the first timer (17.5.7 PWM Mode), so you have to jump back and forth through the document all the time.
PWM on STM8 has an important advantage over Atmega PWM:
PWM with edge alignment
Account configuration from bottom to top
Up counting is active if the DIR bit in the TIM_CR1 register is clear
Example
The example uses the first PWM mode. The PWM reference signal OCiREF is held high as long as TIM1_CNT < TIM1_CCRi. Otherwise, it takes a low level. If the value to compare in the TIM1_CCRi register is greater than the autoload value (TIM1_ARR register), the OCiREF signal is held at 1. If the comparison value is 0, OCiREF is held at zero....
STM8 timer during update event checks first compare value, and only then produces a reference signal. In Atmega, the timer first shivers, and then compares, as a result of which, when compare value == 0 the output is a needle that must be dealt with somehow (for example, by programmatically inverting the logic).
So what we want to do: 8-bit PWM (AR == 255), counting from bottom to top, alignment along the border. Since the bulbs are connected to the chip by cathodes, the PWM should output 0 (LED on) until compare value and 1 after.
We have already read about some PWM mode, so we find the desired register of the second timer by searching in the reference manual for this phrase (18.6.8 - TIMx_CCMR1):
110: First PWM mode - when counting from bottom to top, the first channel is active as long as TIMx_CNT < TIMx_CCR1. Otherwise, the first channel is inactive. [further in the document, erroneous copy-paste from timer 1] 111: Second PWM mode - when counting from bottom to top, the first channel is inactive until TIMx_CNT < TIMx_CCR1. Otherwise, the first channel is active.
Since the LEDs are connected to the MK with cathodes, the second mode suits us (the first one too, but we don’t know this yet).
Bit 3 OC1PE: Enable Preloading Output 1
0: Preload register on TIMx_CCR1 disabled. You can write to TIMx_CCR1 at any time. The new value works immediately.
1: Preload register on TIMx_CCR1 enabled. Read/write operations access the preload register. The preloaded value of TIMx_CCR1 is loaded into the shadow register during each update event.
*Note: Preload registers must be enabled for PWM mode to work properly. This is optional in single signal mode (the OPM bit is set in the TIMx_CR1 register).
Okay, turn on everything you need for the three channels of the second timer:
The second timer can only count from bottom to top, alignment on the border, nothing needs to be changed. Set the frequency divider, for example, to 256. For the second timer, the divider is set in the TIM2_PSCR register and is a power of two:
It remains to turn on the conclusions and the second timer itself. The first task is solved by registers Capture/Compare Enable: there are two of them, three channels are scattered asymmetrically over them. Here we can also learn that it is possible to change the polarity of the signal, i.e. in principle, PWM Mode 1 could also be used. We write:
Let's write a simple analogue of AnalogWrite (), which will pass the actual values to the timer for comparison. The registers are predictably named Capture/Compare registers, there are two of them for each channel: the low 8 bits in TIM2_CCRxL and the high bits in TIM2_CCRxH. Since we started 8-bit PWM, it is enough to write only the low bits:
The attentive reader will notice that we have a slightly defective PWM, unable to give out 100% fill (at a maximum value of 255, the signal is inverted by one timer cycle). For LEDs, this does not play a role, and the attentive reader already guesses how to fix it.
PWM on the second timer works, go to the first.
The first timer has exactly the same bits in the same registers (it's just that those bits that remained "reserved" in the second timer are actively used for all sorts of advanced things in the first). Therefore, it is enough to find the addresses of the same registers in the datasheet and copy the code. Well, change the value of the frequency divider, because. the first timer wants to get not a power of two, but an exact 16-bit value in two registers Prescaler High и low. We do everything and ... the first timer does not work. What's the matter?
The only way to solve the problem is by looking at the entire section about the control registers of timer 1, where we are looking for one that the second timer does not have. there will be 17.7.30 Break register (TIM1_BKR), where there is a bit like this:
The third mini-project is to connect eight RGB LEDs to the second timer in PWM mode and make them show different colors. It is based on the concept of LED multiplexing, which consists in the fact that if you turn on and off the LEDs very, very quickly, it will seem to us that they are constantly on (persistence of vision, inertia of visual perception). I once did something like that on arduino.
The algorithm of work looks like this:
connected the anode of the first RGB LED;
lit it, giving the necessary signals to the cathodes;
waited for the end of the PWM cycle;
connected the anode of the second RGB LED;
lit it...
Well, etc. Of course, for beautiful work, it is required that the anode connection and the “ignition” of the LED occur simultaneously. Well, almost. In any case, we need to write a code that will output values in three channels of the second timer, change them when UEV is reached and at the same time change the currently active RGB LED.
Since the LED switching is automatic, we need to create "video memory" from where the interrupt handler will receive data. This is a simple array:
uint8_t colors[8][3];
In order to change the color of a particular LED, it will be enough to write the necessary values \uXNUMXb\uXNUMXbinto this array. And the variable will be responsible for the number of the active LED
uint8_t cnt;
Demux
For proper multiplexing, we need, oddly enough, the CD74HC238 demultiplexer. Demultiplexer - a chip that implements the operator in hardware <<. Through three input pins (bits 0, 1 and 2) we feed him a three-bit number X, and in response he activates the output number (1<<X). The remaining inputs of the chip are used to scale the entire design. We need this chip not only to reduce the number of occupied pins of the microcontroller, but also for safety - so as not to accidentally turn on more LEDs than possible and not burn the MK. The chip costs a penny, it should always be kept in the first-aid kit at home.
CD74HC238 will be responsible for supplying voltage to the anode of the desired LED. In a full-fledged multiplex, it would supply voltage to the column through the P-MOSFET, but in this demo, you can do it directly, because. it pulls 20mA, according to absolute maximum ratings in the datasheet. From datasheet CD74HC238 we need a pinout and this cheat sheet:
H = high voltage level, L = low voltage level, X - don't care
We connect E2 and E1 to ground, E3, A0, A1 and A3 to pins PD5, PC3, PC4 and PC5 of STM8. Since the table above contains both low and high levels, we set up these pins as push-pull pins.
PWM
PWM on the second timer is configured in the same way as in the previous story, with two differences:
First, we need to enable the interrupt on Update Event (UEV) which will call a function to toggle the active LED. This is done by changing the bit Update Interrupt Enable in a register with a speaking name
The second difference is connected with such a phenomenon of multiplexing as ghosting - parasitic glow of diodes. In our case, it may appear due to the fact that the timer, having caused an interrupt on the UEV, goes on ticking, and the interrupt handler does not have time to switch the LED before the timer starts writing something to the outputs. To combat this, you will have to invert the logic (0 = maximum brightness, 255 = nothing is on) and not allow extreme duty cycle values. Those. ensure that after UEV the LEDs are completely extinguished for one PWM cycle.
Avoid setting r, g and b to 255 and remember to invert them when using.
Interrupts
The essence of the interrupt is that, under certain circumstances, the chip stops executing the main program and calls some external function. Interrupts occur due to external or internal influences, including from the timer.
When we first created a project in ST Visual Develop, apart from main.c we got a window with a mysterious file stm8_interrupt_vector.cautomatically included in the project. In this file, a function is attached to each interrupt NonHandledInterrupt. We need to bind our function to the desired interrupt.
The datasheet has a table of interrupt vectors, where we find the ones we need:
13 TIM2 update/overflow
14 TIM2 Capture/Compare
We need to change the LED at UEV, so interrupt #13 is needed.
Accordingly, firstly, in the file stm8_interrupt_vector.c change the name of the function responsible for interrupt number 13 (IRQ13) by default to our own:
{0x82, TIM2_Overflow}, /* irq13 */
Secondly, we will have to create a file main.h such content:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // вырубаем демультиплексор
PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
PD_ODR |= (1<<5); // включаем демультиплексор
TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending
cnt++;
cnt &= 7; // двигаем счетчик LED
TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}
It remains to enable interrupts. This is done with an assembler command. rim - you will have to look for it in Programming Manual:
//enable interrupts
_asm("rim");
Another assembler instruction - sim - Turns off interrupts. They must be disabled while new values are being written to the "video memory" so that an interrupt caused at an unfortunate moment does not spoil the array.