📜 ⬆️ ⬇️

Read datasheets 2: SPI on STM32; PWM, timers and interrupts on STM8


In the first part, I tried to tell hobby electronics engineers who grew out of Arduino's pants, how and why they should read datasheets and other documentation for microcontrollers. The text was great, so I promised to show practical examples in a separate article. Well, he called himself a load ...


Today, I will show you how to solve fairly simple but necessary tasks for many projects on STM32 (Blue Pill) and STM8 controllers using datasheets. All demo projects are dedicated to my favorite LEDs, we will light them in large quantities, for which you will have to use any interesting peripherals.


The text was again huge, so for convenience I make the content:


STM32 Blue Pill: 16 LEDs with DM634 driver
STM8: Configuring Six PWM Pins
STM8: 8 RGB LEDs on three pins, interrupts


Disclaimer: I am not an engineer, I do not pretend to have a deep knowledge of electronics, the article is intended for fans like me. In fact, as a target audience, I considered myself two years ago. If someone told me then that datasheets on an unfamiliar chip are not terrible to read, 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 tape.


The focus of this article is datasheets, not projects, so the code may not be too coiffed and often broken. 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 immersion in a hobby.


STM32


16 LEDs with DM634 and SPI


A small project using the Blue Pill (STM32F103C8T6) and the DM634 LED driver. With the help of datasheets, we will deal with the driver, STM IO-ports and configure SPI.


DM634


A Taiwanese chip with 16 16-bit PWM outputs can be chained together. The younger 12-bit model is known from the domestic Lightpack project. At one time, choosing between the DM63x and the well-known TLC5940, I stopped at the DM for several reasons: 1) TLC on Aliexpress is fake, but this one is not; 2) DM has autonomous PWM with its own frequency generator; 3) it was cheap to buy in Moscow, and not wait for a parcel with Ali. And, of course, it was interesting to learn how to manage the chip yourself, and not to use a ready-made library. The chips are now mainly represented in the SSOP24 package, they are easy to solder onto the adapter.


Since the manufacturer is Taiwanese, the chip datasheet is written in Chinese English, which means it will be fun. First we look at the pinout ( Pin Connection ) in order to understand which leg to connect to and pin description ( Pin Description ). 16 conclusions:



Sources of inflowing direct current (open drain)


Sink / Open-drain output - drain; source of inflowing current; the output, which is actively connected to ground, - 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 conclusions in drain mode is common.



External resistors between REXT and GND for setting the output current value


A reference resistor is installed between the pin REXT and ground, which controls the internal resistance of the outputs, see the graph on page 9 of the datasheet. In DM634, this resistance can also be controlled programmatically by setting the global brightness ( global brightness ); I will not go into details in this article, I’ll just put a 2.2-3k ohm resistor here.


To understand how to control the chip, look at the description of the device interface:



Yeah, here he is, Chinese English in all its glory. It is problematic to translate, you can understand if you wish, but there is another way - to look at how the connection in the datasheet to the functionally close TLC5940 is described:



... Only three pins are required to enter data into the device. The leading edge of the SCLK signal shifts the data from the SIN pin to the internal register. After all data is loaded, a short high XLAT signal captures the sequentially transmitted data in internal registers. Internal registers are triggered by the signal level of the XLAT gate valve. All data is transmitted by the high bit forward.


Latch - a latch / a latch / a clamp.
Rising edge - the leading edge of the pulse
MSB first - the leading (leftmost) bit forward.
to clock data - transfer data sequentially (bit by bit).


The word latch is often found in the documentation for the chips and is translated variously, so for understanding I will allow myself

small educational program
The LED driver is essentially a shift register. The “shift” ( shift ) in the title is a bit-wise movement of data inside the device: each new bit is thrust inside the bit and shoves the whole chain ahead of it. Since during the shift no one wants to observe the chaotic flashing of the LEDs, the process takes place in the buffer registers separated from the workers by the latch - a kind of waiting room where the bits are arranged in the correct sequence. When everything is ready, the flap opens and the bits are sent to work, replacing the previous batch. The word latch in the documentation for the chips almost always implies such a valve, in whatever combinations it is used.

So, the data transfer in 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 next bit value, pull the DCK; and so on, until all the bits have been transmitted ( clocked in ), after which we pull the LAT. This can be done manually ( bit-bang ), but it is better to use the specially designed SPI interface, 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 frighten. At the same time, for reasons of power saving, at the start almost all peripherals are turned off, and the clock frequency is 8 MHz from an internal source. Fortunately, the STM programmers wrote the 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 clocking (but it is possible if we really want to ). But you have to turn on the periphery.


Documentation: on the Blue Pill, the popular STM32F103C8T6 chip is installed, there are two useful documents for it:



In the datasheet we may be interested in:




NB: in the picture from the Internet there was an error noticed in the comments, thanks for that. The picture has been replaced, but this is a lesson - it’s better to check information from datasheets.


Datashit remove, open the Reference Manual, now use only him.
Procedure: deal with standard I / O, configure SPI, turn on the required peripherals.


Input Output


Atmega328 I / O is extremely easy to implement, which is why the abundance of STM32 options can be confusing. Now we need only conclusions, but even they have four options:



open drain output, push-pull output, alternative push-pull, alternative open drain


Push-pull - the usual output from Arduine, pin can be either HIGH or LOW. But with the "open drain" there are difficulties , although in reality everything is simple here:




Output configuration / when the port is assigned to output: / output buffer is on: / - open drain mode: “0” in the output register activates 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 the N-MOS, “1” in the output register activates the P-MOS.


All the difference between open drain and push-pull is that in the first pin it cannot take the HIGH state: when writing a unit to the output register, it goes into high resistance mode ( high impedance , Hi -Z ) When writing a zero pin in both modes behaves the same way, both logically and electrically.


In normal output mode, the pin simply translates the contents of the output register. In the "alternative", it is controlled by the appropriate peripherals (see 9.1.4):



If the port bit is configured as an alternative function output, the output register is disabled, and the pin is connected to the peripheral output signal


An alternative functionality of each pin is described in the Pin Definitions datasheet and is on the downloaded image. To the question what to do if a pin has several alternative functions, the answer is given by a footnote in the datasheet:



If several peripheral blocks use the same pin, in order to avoid conflict between alternative functions, only one peripheral block should be used at the same time, switching using the peripheral clock activation bit (in the corresponding RCC register).


Finally, pins in output mode have a clocking speed. This is another trick of energy saving, in our case we just set the maximum and forget.


So: we use SPI, which means that two pins (with data and a clock signal) should be “an alternative push-pull function”, and another (LAT) - “a normal push-pull” function. But before you assign them, let's deal with SPI.


SPI


Another small educational program

SPI or Serial Peripherial Interface (Serial Peripheral Interface) is a simple and very effective interface for communicating with other MCs and the external world in general. The principle of its operation is already described above, where about the Chinese LED driver (see section 25 in the reference manual). SPI can work in master mode ("master") and slave ("slave"). SPI has four base channels, of which not all may be involved:


  • MOSI, Master Output / Slave Input: this pin in the master mode gives, and in the slave mode it receives data;
  • MISO, Master Input / Slave Output: on the contrary, it accepts in the master, in the slave it returns;
  • SCK, Serial Clock: sets the data transfer rate in the master or accepts a clock signal in the slave. In essence, beats off the bits;
  • SS, Slave Select: using this channel, the slave finds out that something is wanted from it. On STM32 is called NSS, where N = negative, i.e. the controller becomes the slave if the channel is ground. Combines well with the Open Drain Output mode, but that's another story.

Like everything else, SPI on the STM32 is rich in functionality, which somewhat complicates its understanding. For example, he can work not only with SPI, but also with the I2S interface, and in the documentation, their descriptions are intermixed, it is necessary to cut off the unnecessary 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 signal and 1 unidirectional data stream):



In this mode, the application uses SPI either in transmit only mode or receive only mode. / The transfer only mode is similar to the duplex mode: data is transmitted via the sending pin (MOSI in master mode or MISO in slave mode), and the receiving pin (MISO or MOSI, respectively) can be used as a normal input / output pin. In this case, it is enough for the application to ignore the Rx buffer (if you read it, there will be no transferred data).


Great, the pin MISO is free, we connect the LAT signal to it. We will deal with Slave Select, which can be controlled programmatically on the STM32, which is extremely convenient. We read the eponymous paragraph of section 25.3.1 SPI General Description:



NSS program control (SSM = 1) / Information on slave selection is contained in the SSI bit of the SPI_CR1 register. External pin NSS remains free for other application needs.


It's time to write to registers. I decided to use SPI2, we look for its base address in the datasheet - in section 3.3 Memory Map:



Well, start:


#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset))) 

We open section 25.3.3 with the telling title "Configuring SPI in master mode":



1. Set the clock frequency of the serial interface with the bits BR [2: 0] in the SPI_CR1 register.


Registers are collected in the reference manual section. The address offset of CR1 is 0x00, by default all bits are reset ( Reset value 0x0000):



The BR bits set the controller clock divider, thereby determining the frequency at which the SPI will operate. The STM32 frequency will be 72 MHz, the LED driver, according to its datasheet, works with a frequency of up to 25 MHz, thus, it is necessary to divide into four (BR [2: 0] = 001).


 #define _SPI_CR1 0x00 #define BR_0 0x0008 #define BR_1 0x0010 #define BR_2 0x0020 _SPI2_ (_SPI_CR1) |= BR_0;// pclk/4 

2. Set the CPOL and CPHA bits to determine the relationship between data transfer and clocking of the serial interface (see diagram on page 240)


Since we are reading the datasheet here, and not reviewing the diagrams, let's better examine 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 timings relationships. The CPOL bit (clock polarity) controls the state of the clock signal when no data is transmitted. This bit controls the master and slave modes. If the CPOL is cleared, the SCK pin in idle mode is low. If the CPOL bit is set, the SCK pin in idle mode is at a high level.
If the CPHA bit is set (clock phase), the high-bit strobe trap is the second front of the SCK signal (downward if CPOL is cleared, or upward if CPOL is set). Data is captured by the second clock change. If the CPHA bit is cleared, the leading edge of the SCK signal acts as the high-level gate trap (descending if CPOL is set, or ascending if CPOL is cleared). The data is recorded by the first change in the clock signal.


Having drunk into this knowledge, we conclude that both bits should remain zeros, since we need the SCK signal to remain low when not in use, and the data to be transmitted on the leading edge of the pulse (see Rising Edge in DM634).


By the way, here we first encountered the vocabulary feature in ST datasheets: in them the phrase “reset bits to zero” is written to reset a bit , and not to clear a bit , as, for example, in Atmega.


3. Set the DFF bit to define 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 unit:


 #define DFF 0x0800 _SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode 

4. Configure the LSBFIRST bit in the SPI_CR1 register to determine the block format.


LSBFIRST, as its name implies, tunes the transmission with the low-order bit forward. But DM634 wants to receive data, starting with the high bit. Therefore, leave discarded.


5. In hardware mode, if input from the NSS pin is required, send a high signal to the NSS pin during the entire byte transmission sequence. In the NSS program mode, set the SSM and SSI bits in the SPI_CR1 register. If the NSS pin should work on output, you only need to set the SSOE bit.


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 remain set only if a high signal is sent to the NSS)


Actually, we assign our SPI master to these bits and turn it on:


 #define MSTR 0x0004 #define SPE 0x0040 _SPI2_ (_SPI_CR1) |= MSTR; //SPI master //  ,  SPI _SPI2_ (_SPI_CR1) |= SPE; 

SPI is configured, let's write the functions that send the bytes to the driver right away. We continue to read 25.3.3 "Configuring SPI in master mode":



Data transfer order
The transfer starts when a byte is written to the Tx buffer.
The data byte is loaded into the shift register in parallel mode (from the internal bus) during the transmission of the first bit, after which the MOSI pin is transmitted in sequential mode, with the first or last bit ahead, depending on the setting of the LSBFIRST bit in the CPI_CR1 register. The TXE flag is set after the data is transferred from the Tx buffer to the shift register , and an interrupt is created if the TXEIE bit in the CPI_CR1 register is set.


I highlighted a few words in translation to draw attention to one feature of the implementation of SPI in STM controllers. On Atmega, the TXE flag ( Tx Empty , Tx is empty and ready to receive data) is set only after all the bytes have gone out . And here this flag is set after the byte is stuck in the internal shift register. Since it is pushed there by all the bits simultaneously (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 the data, i.e. Only the TXE flag will not be enough for us.


This means that we need some more flag. Let's see in 25.3.7 - “Status Flags”:



<...>

BUSY flag
The BSY flag is set and reset by hardware (writing to it does not affect anything). The BSY flag indicates the state of the SPI communication layer.
It is reset:
when the transfer is complete (except for 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 transfer.


Okay, it will come in handy. Find out where the Tx buffer is. To do this, read "Register data SPI":



Bits 15: 0 DR [15: 0] Data Register
Received data or data to transfer.
The data register is divided into two buffers - one for writing (transfer buffer) and the second for reading (reception buffer). Writing to the data register is written to the Tx buffer, and reading from the data register will return the value contained in the Rx buffer.


Well, the status register, where there are flags TXE and BSY:



We write:


 #define _SPI_DR 0x0C #define _SPI_SR 0x08 #define BSY 0x0080 #define TXE 0x0002 void dm_shift16(uint16_t value) { _SPI2_(_SPI_DR) = value; //send 2 bytes while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent } 

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 are not yet able to pull the pin LAT, so back in 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 also differ from other STM chips. Section 9.1 General Description of GPIO:



Each of the general-purpose input / output 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), 16-bit reset register (GPIOx_BRR) and 32-bit blocking register (GPIOx_LCKR).


Unusual, and also rather inconvenient, here are the first two registers, because 16 pins of the port are scattered over them in the format “four bits per brother”. Those. pins from zero to seventh sit in the CRL, and the rest - in the CRH. In this case, the remaining registers successfully fit the bits of all the pins of the port - often remaining half "reserved".


For simplicity, start at the end of the list.


We do not need a blocking register.


The setup and reset registers are rather funny because they partially duplicate each other: you can only write everything in BSRR, where the older 16 bits will reset the pin to zero, and the younger ones to set to 1, or use the BRR, the lower 16 bits of which only reset the pin . I like the second option. These registers are important in that they provide atomic access to the pins:




Atomic installation or reset
You do not need to disable interrupts when programming GPIOx_ODR at the bit level: you can change one or several bits with one atomic APB2 write operation. This is achieved by writing "1" to the set / reset register (GPIOx_BSRR or, for reset only, to GPIOx_BRR), the bit that needs to be changed. Other bits will remain unchanged.


Data registers have quite talking names - IDR = Input Direction Register, input register; ODR = Output Direction Register, output register. In the current draft, we will 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 the CRH:



And we see that it will be necessary to write something in bits from the 20th to the 31st.


We have already figured out what we want from the pins, so here I can do without a screenshot, I’ll just say that MODE sets the direction (input, if both bits are set to 0) and pin speed (we need 50MHz, i.e. both pin in “1”), and CNF sets the mode: usual “push-pull” - 00, “alternative” - 10. By default, as we see above, all pins have the third bit (CNF0) registered, it sets them to floating input mode.


Since I plan to do something else with this chip, for simplicity, I generally define all possible MODE and CNF values ​​for both the lower and upper control registers.


Somehow like this
 #define CNF0_0 0x00000004 #define CNF0_1 0x00000008 #define CNF1_0 0x00000040 #define CNF1_1 0x00000080 #define CNF2_0 0x00000400 #define CNF2_1 0x00000800 #define CNF3_0 0x00004000 #define CNF3_1 0x00008000 #define CNF4_0 0x00040000 #define CNF4_1 0x00080000 #define CNF5_0 0x00400000 #define CNF5_1 0x00800000 #define CNF6_0 0x04000000 #define CNF6_1 0x08000000 #define CNF7_0 0x40000000 #define CNF7_1 0x80000000 #define CNF8_0 0x00000004 #define CNF8_1 0x00000008 #define CNF9_0 0x00000040 #define CNF9_1 0x00000080 #define CNF10_0 0x00000400 #define CNF10_1 0x00000800 #define CNF11_0 0x00004000 #define CNF11_1 0x00008000 #define CNF12_0 0x00040000 #define CNF12_1 0x00080000 #define CNF13_0 0x00400000 #define CNF13_1 0x00800000 #define CNF14_0 0x04000000 #define CNF14_1 0x08000000 #define CNF15_0 0x40000000 #define CNF15_1 0x80000000 #define MODE0_0 0x00000001 #define MODE0_1 0x00000002 #define MODE1_0 0x00000010 #define MODE1_1 0x00000020 #define MODE2_0 0x00000100 #define MODE2_1 0x00000200 #define MODE3_0 0x00001000 #define MODE3_1 0x00002000 #define MODE4_0 0x00010000 #define MODE4_1 0x00020000 #define MODE5_0 0x00100000 #define MODE5_1 0x00200000 #define MODE6_0 0x01000000 #define MODE6_1 0x02000000 #define MODE7_0 0x10000000 #define MODE7_1 0x20000000 #define MODE8_0 0x00000001 #define MODE8_1 0x00000002 #define MODE9_0 0x00000010 #define MODE9_1 0x00000020 #define MODE10_0 0x00000100 #define MODE10_1 0x00000200 #define MODE11_0 0x00001000 #define MODE11_1 0x00002000 #define MODE12_0 0x00010000 #define MODE12_1 0x00020000 #define MODE13_0 0x00100000 #define MODE13_1 0x00200000 #define MODE14_0 0x01000000 #define MODE14_1 0x02000000 #define MODE15_0 0x10000000 #define MODE15_1 0x20000000 

Our pins are located on port B (base address - 0x40010C00), code:


 #define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset))) #define _BRR 0x14 #define _BSRR 0x10 #define _CRL 0x00 #define _CRH 0x04 //  SPI2: MOSI  B15, CLK  B13 //LAT     MISO – B14 //  ,      _PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0); //   MOSI  SCK _PORTB_ (_CRH) |= CNF15_1 | CNF13_1; //50 , MODE = 11 _PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0; 

And, accordingly, you can write defains for LAT, which will be jerked by the BRR and BSRR registers:


 /*** LAT pulse – high, then low */ #define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14) #define LAT_low() _PORTB_(_BRR) = (1<<14) 

(LAT_low is just by inertia, somehow it always has been, let it remain for itself)


Now everything is great, just not working. Because it is an STM32, it saves electricity, which means that it is necessary to turn on the clocking of the necessary peripherals.


Turn on clocking


The clocks are 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 (control reset and clocking).


As mentioned above, fortunately, the most difficult part of the clocking theme was made for us from STM, for which many thanks to them (once again I will provide a link to the Di Halt website , so that it is clear how confused this is). We need only the registers that are responsible for enabling the Peripheral Clock Enable Registers. First we find the base address of the RCC, it is at the very beginning of the "Memory Card":



 #define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset))) 

And then either click on the link where to try to find something in the tablet, or, much better, run through the descriptions of the enable registers from the sections about enable registers . Where we find RCC_APB1ENR and RCC_APB2ENR:




And in them, respectively, the bits that include clocking SPI2, IOPB (I / O Port B) and alternative functions (AFIO).


 #define _APB2ENR 0x18 #define _APB1ENR 0x1C #define IOPBEN 0x0008 #define SPI2EN 0x4000 #define AFIOEN 0x0001 //   B  .  _RCC_(_APB2ENR) |= IOPBEN | AFIOEN; //  SPI2 _RCC_(_APB1ENR) |= SPI2EN; 

The final code can be found here .


If there is an opportunity and desire to potest, 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 land.



STM8 PWM


PWM on STM8


When I was just planning this article, I decided for an example to try to master some functionality of an unfamiliar chip using datasheet only, so that the shoemaker without boots would not work. STM8 was ideally suited for this role: firstly, I had a couple of Chinese boards with STM8S103, and secondly, it was not very popular, and therefore the temptation to read and find a solution on the Internet rests on the absence of these very solutions.


The chip also has a datasheet and reference manual RM0016 , in the first pinout and addresses of registers, in the second - everything else. STM8 is programmed in C in the ugly IDE ST Visual Develop .


Clocking and I / O


By default, the STM8 operates at a frequency of 2 MHz, this should be immediately corrected.



HSI Clock (High Speed ​​Internal)
The HSI clock signal is obtained from an internal 16 MHz RC oscillator with a programmable divider (from 1 to 8). It is set in the clock divider register (CLK_CKDIVR).
Note: at the start, the HSI RC generator with a divider 8 is selected as the leading clock source.


Find the address of the register in the datasheet, the description in the refman and see that the register must be cleared:


 #define CLK_CKDIVR *(volatile uint8_t *)0x0050C6 CLK_CKDIVR &= ~(0x18); 

Since we are going to start the PWM and connect the LEDs, we look at pinout:



The chip is small, many functions are suspended on the same pins. The fact that 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 not necessary, because new functionality is activated only after a reboot. It is easier to use ST Visual Programmer (swings with Visual Develop), which can change these bytes. The pinout shows that the pins CH1 and CH2 of the first timer are hidden in square brackets; In STVP, you need to set the AFR1 and AFR0 bits, and the second one will also transfer the CH1 pin 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.


Configuring the I / O pins themselves on the STM8 is simpler and more logical than on the STM32:



 #define PA_DDR *(volatile uint8_t *)0x005002 #define PA_CR2 *(volatile uint8_t *)0x005004 #define PD_DDR *(volatile uint8_t *)0x005011 #define PD_CR2 *(volatile uint8_t *)0x005013 #define PC_DDR *(volatile uint8_t *)0x00500C #define PC_CR2 *(volatile uint8_t *)0x00500E PA_DDR = (1<<3); //output PA_CR2 |= (1<<3); //fast PD_DDR = (1<<3); //output PD_CR2 |= (1<<3); //fast PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast 

PWM setting


To begin with, we define the terms:



As is clear from pinouts, two timers have the PWM capabilities - the first and the 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, in order not to accidentally use something that is not in it. Some problem is that the description of the PWM functional of all the timers in the reference manual is in the chapter on the first timer (17.5.7 PWM Mode), so you always have to jump back and forth on the document.


PWM on STM8 has an important advantage over PWM Atmega:



PWM with alignment on the border
Bottom Up Configuration
The bottom-up count is active if the DIR bit in the TIM_CR1 register is cleared.
Example
The example uses the first PWM mode. The PWM OCiREF reference signal is held at a high level while TIM1_CNT <TIM1_CCRi. Otherwise, it takes a low level. If the value for comparison in the TIM1_CCRi register is larger than the auto-load value (TIM1_ARR register), the OCiREF signal is held at 1. If the value for comparison is 0, OCiREF is held at zero. ...


The STM8 timer during the update event first checks the compare value , and only then issues a reference signal. Atmegie has a timer at first sharashit, and then compares, with the result that when compare value == 0 , a needle is obtained at the output, with which we must somehow fight (for example, programmatically inverting logic).


So, what we want to do: 8-bit PWM ( AR == 255 ), we consider the bottom-up alignment along the border. Since the light bulbs are connected to the chip by cathodes, the PWM must output 0 (LED is on) to 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 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 while TIMx_CNT <TIMx_CCR1. Otherwise, the first channel is inactive. [further in the document erroneous copy-paste from timer 1]
111: Second mode PWM - when counting from the bottom up, the first channel is inactive, while TIMx_CNT <TIMx_CCR1. Otherwise, the first channel is active.


Since the LEDs are connected to MK cathodes, the second mode is suitable for us (the first one too, but we do not know it yet).



Bit 3 OC1PE: Enable Preloading Output 1
0: The preload register on TIMx_CCR1 is disabled. You can write in TIMx_CCR1 at any time. New value works immediately.
1: Preloading register on TIMx_CCR1 is enabled. Read / write operations refer to the preload case. The preloaded TIMx_CCR1 value is loaded into the shadow register during each update event.
* Note: for correct operation of the PWM mode, the preloading registers must be enabled. This is optional in single signal mode (the OPM bit is set in the TIMx_CR1 register).

OK, turn on everything you need for the three channels of the second timer:


 #define TIM2_CCMR1 *(volatile uint8_t *)0x005307 #define TIM2_CCMR2 *(volatile uint8_t *)0x005308 #define TIM2_CCMR3 *(volatile uint8_t *)0x005309 #define PWM_MODE2 0x70 //PWM mode 2, 0b01110000 #define OCxPE 0x08 //preload enable TIM2_CCMR1 = (PWM_MODE2 | OCxPE); TIM2_CCMR2 = (PWM_MODE2 | OCxPE); TIM2_CCMR3 = (PWM_MODE2 | OCxPE); 

AR consists of two eight-bit registers, everything is simple:


 #define TIM2_ARRH *(volatile uint8_t *)0x00530F #define TIM2_ARRL *(volatile uint8_t *)0x005310 TIM2_ARRH = 0; TIM2_ARRL = 255; 

The second timer can only count from the bottom up, alignment along the border, nothing needs to be changed. Set the frequency divider, for example, at 256. At the second timer, the divider is set in the TIM2_PSCR register and is a power of two:


 #define TIM2_PSCR *(volatile uint8_t *)0x00530E TIM2_PSCR = 8; 

It remains to include the findings and the second timer itself. The first task is solved by the registers Capture / Compare Enable : there are two of them, three channels are scattered across them asymmetrically. Here we can also learn that the polarity of the signal can be changed, i.e. in principle, it was possible to use PWM Mode 1. We write:


 #define TIM2_CCER1 *(volatile uint8_t *)0x00530A #define TIM2_CCER2 *(volatile uint8_t *)0x00530B #define CC1E (1<<0) // CCER1 #define CC2E (1<<4) // CCER1 #define CC3E (1<<0) // CCER2 TIM2_CCER1 = (CC1E | CC2E); TIM2_CCER2 = CC3E; 

And finally, we start the timer in the TIMx_CR1 register:



 #define TIM2_CR1 *(volatile uint8_t *)0x005300 TIM2_CR1 |= 1; 

AnalogWrite(), . Capture/Compare registers , : 8 TIM2_CCRxL TIM2_CCRxH. 8- , :


 #define TIM2_CCR1L *(volatile uint8_t *)0x005312 #define TIM2_CCR2L *(volatile uint8_t *)0x005314 #define TIM2_CCR3L *(volatile uint8_t *)0x005316 void setRGBled(uint8_t r, uint8_t g, uint8_t b) { TIM2_CCR1L = r; TIM2_CCR2L = g; TIM2_CCR3L = b; } 

, , 100% ( 255 ). , , .


, .


( , «» , ). . , .. , 16- Prescaler High Low . … . What's the matter?


1, , . 17.7.30 Break register (TIM1_BKR) , :




 #define TIM1_BKR *(volatile uint8_t *)0x00526D TIM1_BKR = (1<<7); 

, .



STM8 Multiplex


STM8


- , RGB- . – LED-, , - , , ( persistence of vision , ). - - .


:



Well, etc. , , «» . . , , , UEV RGB-.


LED , «», . :


 uint8_t colors[8][3]; 

, , .


 uint8_t cnt; 


, , CD74HC238. – , << . ( 0, 1 2) X, ( 1<<X ). . , – , , . , .


CD74HC238 , . P-MOSFET, , .. 20 , absolute maximum ratings . CD74HC238 :



H = , L = , X –


E2 E1 , E3, A0, A1 A3 PD5, PC3, PC4 PC5 STM8. , , push-pull .



, , :


-, Update Event (UEV), , LED. Update Interrupt Enable




 #define TIM2_IER *(volatile uint8_t *)0x005303 //enable interrupt TIM2_IER = 1; 

, ghosting – . - , , UEV, , LED - . (0 = , 255 = ) . Those. , UEV .


:


 //set polarity TIM2_CCER1 |= (CC1P | CC2P); TIM2_CCER2 |= CC3P; 

r, g b 255 .



, - . - , .


ST Visual Develop, main.c stm8_interrupt_vector.c , . NonHandledInterrupt . .


, :



13 TIM2 /
14 TIM2 /


LED UEV, â„–13.


, -, stm8_interrupt_vector.c , â„–13 (IRQ13) :


 {0x82, TIM2_Overflow}, /* irq13 */ 

-, main.h :


 #ifndef __MAIN_H #define __MAIN_H @far @interrupt void TIM2_Overflow (void); #endif 

, , main.c :


 @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; } 

. rim – Programming Manual :


 //enable interrupts _asm("rim"); 

– sim – . «», .


– .



- , , . , .


')

Source: https://habr.com/ru/post/456094/


All Articles