In the
first part, we sorted out how to flash the selected MK, how to properly configure it, and also learned how to work with digital ports.
Now it's time to look at the rest of the microcontroller's periphery.
All parameters of the MC are set through the installation of certain values in the registers for special purposes (SFR).
Like the configuration bits, all the registers existing in the selected MK are available to us in the form of variables thanks to the connected library.
To find out which bits in which registers we need for a particular module, we will have to look into the documentation again.
For an example, take a look at the table of registers related to digital inputs / outputs of port B:

')
Having read the values of the
PORTB register, we get the current logical level on each leg of the port.
Writing to the register sets the specified level on the corresponding legs of the port.
Each bit of the register is available to us as a separate variable; it is through them that we controlled the LED and read the state of the button.
The
TRISB register
is responsible for sending data through the port. Each of the 8 bits of the register is tied to the corresponding leg MK.
Assigning the unit to the desired bit - we will make an input from it, and assigning zero to the output.
It is in this register that we changed the bits through the pin_Bx_direction variables.
In the
OPTION_REG register, only the high-order bit refers to the port:
RBPU : PORTB Pull-up Enable bit
1 = PORTB pull-ups are disabled
0 = PORTB pull-ups
This bit is responsible for connecting the internal braces, which was mentioned in the first part.
According to the table it can be seen that the lift is initially turned off, which means that in the absence of an external pull-up it is necessary to turn on the inner one yourself:
OPTION_REG_NRBPU = 0
If you want to know the principle of operation of a particular module, you can always find a closer diagram in the documentation.
Interruptions
Since microcontrollers do not support multitasking, a number of problems arise in combining several processes.
Suppose we need to flash one LED constantly with a long period, and the second switch at the touch of a button.
Whatever order of actions we choose, how it should work will not work for us: for while the microcontroller counts down the time until the switching of the first LED, it can skip the fact of pressing the button.
This is where interrupts come in to help us.
Under certain conditions, the microcontroller can interrupt the execution of an infinite loop and execute a small subroutine, and then return to the execution of the main task.
In our chosen MK 16f628a there are 10 possible sources of interruptions:
- external interrupt source int
- change in signal level on digital inputs RB4: 7
- TMR0 timer overflow
- TMR1 timer overflow
- match TMR2 and PR2
- completion of writing to EEPROM
- change comparator output level
- receiving \ completion of sending data via USART
- CCP interrupts
An interrupt for each source can be either
enabled or
disabled by changing the corresponding bits in the
INTCON and
PIE1 registers
individually .
To enable interrupts controlled by the PIE1 register, it is necessary to enable peripheral interrupts with the PEIE bit in the
INTCON register.
After selecting the necessary interrupt sources, it is necessary to globally enable interrupts with the
GIE bit in the
INTCON register.
For each interrupt, there is another bit in the
INTCON register or
PIR1 - the interrupt flag.
When an interrupt is triggered, the corresponding flag is assigned the value 1, by which it is easy to determine which of the interrupts has worked.
It is necessary to reset the interrupt flags manually after entering the interrupt handler, otherwise with several sources it will be impossible to parse who exactly caused it.
As an example of using interrupts, let's rewrite our program for flashing LEDs.
We use the source of interrupts INT. Depending on the state of the INTEDG bit in the OPTION register, an interrupt will be generated either on the leading edge of the signal (transition from low to high) or on the rear.
To change the signal level to INT, it is not necessary to move the button to the corresponding leg (pin 6).
include 16f628a -- target PICmicro
--
pragma target clock 4_000_000 -- ,
--
pragma target OSC INTOSC_NOCLKOUT --
pragma target WDT disabled --
pragma target PWRTE disabled --
pragma target MCLR external --
pragma target BROWNOUT disabled --
pragma target LVP disabled --
pragma target CPD disabled -- EEPROM
pragma target CP disabled --
--
enable_digital_io() -- \
--
alias led is pin_B5 -- RB5
pin_B5_direction = output -- RB5
--
alias button is pin_B0 -- RB0
pin_B0_direction = input -- RB0
var volatile bit led_blink = false --
--
INTCON_INTE = on -- INT
INTCON_INTF = off -- INT
OPTION_REG_INTEDG = 0 -- 1->0
INTCON_GIE = on --
-- INT
procedure INT_ISR is
pragma interrupt
if INTCON_INTF then --
INTCON_INTF = off --
led_blink = !led_blink --
end if
end procedure
led = off --
forever loop
led = off --
_usec_delay ( 100000 ) -- 0,1
if led_blink then --
led = on -- 0,1
_usec_delay ( 100000 ) -- 0,1
end if
end loop
Alas, this is not the best option for two reasons:
- because of the bounce of the contacts of the button, the interruption can work several times in a row, it is desirable to suppress the bounce by software
- in the main program loop, we still have pauses, during which the microcontroller does nothing but wait.
Timers
The main work of timers is counting. Upon completion of the account, they can generate an interrupt. And since the counting is hardware, without clogging up CPU time with a wait, the timers are well suited to replace our pauses.
Each of the three timers has its own characteristics, so for performing certain tasks you need to be able to choose the most suitable one.
TMR0
- 8-bit timer (counts from 0 to 255)
- clocked either from the system frequency or from an external source
- can read both leading and trailing edges of a clocked signal
- 8-bit prescaler (can read every second, every 4 ... every 256 signal)
- interrupt is generated when overflowed (when going from 255 to 0)
- the timer runs constantly
What does this give us?
When clocked from the system frequency (in our case - 4 MHz / 4 = 1 Mhz), the timer will generate interrupts with a constant frequency.
It is not difficult to calculate that without a prescaler, interrupts will have a frequency of 3906.25 Hz. For the LED - a bit too much.
The prescaler can decrease the frequency eight times by an order of magnitude (double, i.e. by 2 times).
With the prescaler 1: 256 we get a frequency of 15.3 Hz. LED flashing with such a frequency is quite visible to the human eye.
When clocking MK from external quartz, you can achieve a different frequency range.
When clocking a timer from an external source, the timer turns into a counter of external pulses. In principle, the accounts do not change, just depending on the source, there may not be a constant frequency of interruptions. A counter can be considered as the number of button presses, wheel revolutions, etc. At the same time, no one is obligated to count from zero to an interruption: the current value of the counter is always available both for reading and for writing.
If desired, in the timer mode, you can increase the interrupt frequency by changing the start value of the counter with each interruption, but since it takes an unknown time during the transition to the processing procedure, it will not be possible to accurately calculate the frequency.
TMR1
The main differences of the timer from TMR0:
- this timer is 16 bit
- the timer can be clocked not only from an external source, but also from an additional watch quartz
- maximum available prescaler - 1: 8
- the timer can read only the leading edges of the signal
- timer can be used by the CCP module
- the timer can be turned off
You can use TMR1 in the same way as TMR0: either to generate a specific frequency, or to count pulses.
TMR2
This 8-bit timer has a slightly different principle of operation.
It can clock only from the system frequency. The prescaler can only be set to values 1: 1, 1: 4 or 1:16.
The received pulses timer counts from zero to a pre-set value of PR2.
After the match between TMR2 and PR2, a signal is sent to the 4-bit post-splitter, and only after the post-splitter is full, an interrupt is generated.
Thanks to this scheme, it is possible to adjust the final interrupt frequency with a minimum step.
In addition to the post-divider, the signal when PR2 matches can go to the CCP module as the PWM timing base.
Like TMR1, this timer can be disabled.
Usage example
As an example, let's set the flashing frequency of the LED with the timer TMR1.
The list of all necessary variables can be found in the table:

A description of each bit can be found in the documentation for the microcontroller.
include 16f628a -- target PICmicro
--
pragma target clock 4_000_000 -- ,
--
pragma target OSC INTOSC_NOCLKOUT --
pragma target WDT disabled --
pragma target PWRTE disabled --
pragma target MCLR external --
pragma target BROWNOUT disabled --
pragma target LVP disabled --
pragma target CPD disabled -- EEPROM
pragma target CP disabled --
--
enable_digital_io() -- \
--
alias led is pin_B5 -- RB5
pin_B5_direction = output -- RB5
--
--
T1CON_T1CKPS = 0b_11 -- , 2
T1CON_TMR1CS = 0 --
PIE1_TMR1IE = on -- TMR1
PIR1_TMR1IF = off -- TMR1
INTCON_PEIE = on --
T1CON_TMR1ON = on --
INTCON_GIE = on --
--
; Fosc/4 : 4MHz/4 = 1 Mhz
; 1:8 : 1Mhz/8 = 125 kHz
; - 16 : 125 kHz/65536 = 1.9 Hz
; 2 : 0,95 Hz
--
-- TMR1
procedure TMR1_ISR is
pragma interrupt
if PIR1_TMR1IF then --
PIR1_TMR1IF = off --
led = !led --
end if
end procedure
forever loop
--
end loop
CCP
The CCP module (Capture / Compare / PWM) is designed for measuring and generating pulse signals.
Capture
In capture mode, the module uses TMR1 as a time meter. As soon as a monitored event occurs on the pin of CCP1 (pin 9), the module will save the current 16-bit value of TMR1 to the registers CCPR1H: CCPR1L.
These events can be:
- each leading edge of the signal
- each leading edge of the signal
- every fourth front
- every 16 front front
By combining events and calculating the difference between the obtained timer values, you can obtain signal data such as period, pulse duration, or duty cycle. For example, some accelerometers transmit information about the resulting acceleration by changing the signal's duty cycle.
Compare
In the compare mode, the module works in the opposite direction: as soon as the value in the CCPR1H: CCPR1L registers coincides with the current TMR1 value, the module can set 1 or 0 on the CCP1 pin (pin 9) or simply generate an interrupt. Also, if a match occurs, the module may reset TMR1.
By measuring the required time intervals, it is possible to form pulses of a certain shape. For example, to control the position of a servo, you need to feed high-level pulses of 700-2,200 μs with a frequency of 50 Hz to the signal line. Depending on the pulse length, the servo will set its position either to one extreme position (700 μs), or to another (2200 μs), or approximately in the center (1500 μs).
Pwm
In the PWM mode, the module independently generates a signal with a frequency generated by the TMR2 timer and a specified 10-bit duty cycle.
What is a PWM signal?
The microcontroller can produce only a digital signal - logical 1 and 0.
In the PWM signal with a constant frequency, the first part of the period is fed to the output 1, and the second part - 0. By varying the ratio of the duration of both parts, the duty ratio changes. Durability PWM is the ratio of the pulse duration of a logical unit and the PWM period. 10-bit PWM can ensure the accuracy of changing the duty cycle in 1/1024 period duration.

How can this be used?
Since the frequency of the signal is large enough, it will seem to low-speed loads that they receive a voltage equal to the percentage of duty ratio of the maximum. Thus, from the PWM we will have a normal analog output with a voltage range from 0 to Vdd (in our case - 5V).
For high-speed loads (for example, for LEDs) the PWM signal will adjust the percentage of running time. For the human eye, the flashing of the LED with a high frequency and a variable porosity will seem to be a constant light with variable brightness.
There is a library to work with PWM, so we will not need much effort to calculate and configure the registers.
Example of using the library:
--
pin_CCP1_direction = output -- pin 9
include pwm_hardware -- ,
pwm_max_resolution( 4 ) -- TMR2 (976 Hz)
pwm1_on() --
var bit fade_type = 1 --
var byte i = 0 --
forever loop
--
if fade_type then
i = i + 1
if i == 100 then
fade_type = 0
end if
else
i = i - 1
if i == 0 then
fade_type = 1
end if
end if
pwm1_set_percent_dutycycle(i) --
_usec_delay ( 20000 ) -- ,
end loop
Comparators
The whole job of the comparator is to compare two voltages and say which one is greater. The comparison takes place continuously (when the module is turned on), and an interrupt can be generated when the comparison result changes.

Depending on the settings, comparators can operate in eight modes:

The diagrams clearly show which voltages are compared in each mode, it’s worth explaining what the reference voltage
Vref is .
Voltage reference
This is another small module, usually required only for the operation of comparators.
The only task of the module is to divide the supply voltage to the desired value.
The module is a simple divider on 16 resistors. All that he can do is to isolate the voltage that has been lowered to the desired value from the power supply.
When powered at 5V, the module can supply a voltage from 0 to 3.6V.
Eeprom
In the microcontroller 16f628a we have 128 bytes of non-volatile memory.
When using an assembler, we would have to read a lot about the order of writing and reading in memory, we only need to connect one library to work with eeprom.
To connect the library is enough to write
include pic_data_eeprom
after which a number of procedures and functions become available to us:
data_eeprom_read ([offset], [byte]) - the procedure reads byte number [offset] and
- puts the result in the variable [byte]
data_eeprom_write ([offset], [byte]) - the procedure writes the resulting byte [byte] to the place [offset]
data_eeprom_read_word ([offset], [word]) - the procedure reads 2 bytes: [offset] and [offset] +1
data_eeprom_write_word ([ofset], [word]) - the procedure writes 2 bytes in a row
data_eeprom_read_dword ([offset], [dword]) - the procedure reads 4 bytes in a row
data_eeprom_write_dword ([offset], [dword]) - the procedure writes 4 bytes in a row
[byte] = data_eeprom ([offset]) - reading a byte through a function
[word] = data_eeprom_word ([offset]) - read two bytes through the function
[dword] = data_eeprom_dword ([offset]) - read 4 bytes through the function
The only thing to remember is the size of the memory. Write dword at offset 128 in this case will not succeed.
Usart
USART is a serial I / O port. This module is designed to connect the microcontroller with other devices.
To organize a communication channel, all you need to do is connect the
Rx of each device with the
Tx of the other.
If you want to customize the mode of operation of the module, you can examine in detail the documentation for the microcontroller, but again we will need only one library.
The only thing we need to specify is the data transfer rate. The maximum speed depends on the clock signal MK. At 4 MHz, the recommended speed is 2400.
const serial_hw_baudrate = 2400 --
include serial_hardware --
serial_hw_init() --
After setup, you can begin to receive and transmit bytes.
serial_hw_write ([byte]) - the procedure for sending bytes [byte]
serial_hw_data = [byte] - sending a byte through a pseudo-variable
serial_hw_read ([byte]): [boolean] - if there is a byte sent, puts the value in
- variable [byte] and returns true
- if there is no data received, returns false
serial_hw_data_available - if there are received bytes, this variable returns true, otherwise - false
[byte] = serial_hw_data - reading a byte through a pseudo-variable, in the absence of
- received microcontroller bytes will be waiting for them
- when using this method of reading it is necessary to check
- the fact of the arrival of data
To communicate with a computer, you can use UART-COM and UART-USB adapters. However, no one bothers to collect them yourself according to the schemes:


Outer quartz
As was seen, external quartz is not always necessary. But there are also such cases when it is required to use an external clock signal.
Connect external quartz to the feet of OSC1 and OSC2 (pin16 and pin 15):

For common quartz resistor is not needed, the capacitance of the capacitors is selected depending on the frequency of the quartz. Also, depending on the frequency, the mode set in the
OSC configuration bit is selected:

Instead of quartz, you can also use a ceramic resonator, it already contains capacitors - to connect the resonator, it is enough to connect the third contact to the ground.
Results
So we reviewed the basic features of each module in the 16f628a microcontroller. Of course, in such a short article it is impossible to describe all the details when working with each module, if necessary, detailed information about each module is available in the documentation for each microcontroller.
With the current set of peripherals, you can run quite a few different devices, but sometimes it is easier to switch to a more complete microcontroller. Thanks to universal libraries, to start working with any other supported MK will not be difficult.