Low Level Appunti PDF
Document Details
Tags
Summary
This document details low-level concepts of microcontrollers and microprocessors, including different types of buses and assembly instructions. It also explores concepts like jumps, loops, subroutine calls, and the use of the stack for program execution. This document covers aspects of computer hardware and software.
Full Transcript
Lesson 2 – 3 March We can have general purpose microprocessors and microcontrollers, that have differences each other. A microprocessor is an integrated circuit with inside CPU and other components that can be useful. It's connected with other components, with bus, like memories and ports. 3 type...
Lesson 2 – 3 March We can have general purpose microprocessors and microcontrollers, that have differences each other. A microprocessor is an integrated circuit with inside CPU and other components that can be useful. It's connected with other components, with bus, like memories and ports. 3 types of buses: DATA BUS ADDRESS BUS CONTROL BUS Microcontrollers are composed by only one integrated circuit with all the components inside, CPU included and also bus. The components are: RAM, ROM, serial ports, I/O, timer, CPU. An AVR microcontroller have some little differences in the structure because, for example, it has a Program Rom that is linked to the CPU via Program BUS. Lesson 5 – 10 March Some commands can change the content of the flow of the execution. Executing a sequential flow is not enough. We want to move the flow if some condition happens: - When we use a conditional instruction (if) - When we make a loop; - When we call a function. In assembly we have 2 groups of instructions that allow the CPU to move the flow: - Jump, for making loops of conditions; - Call, for invoking functions. There are 2 kinds of jumps: - unconditional, without checking conditions, like RJMP and JMP - conditional, when the jump is done only if it checks a condition and it is valid. We have 3 unconditional jumps: RJMP, JMP and IJMP. We use them to pass the code specified by a label. RJMP = RELATIVE JUMP. There are 3 ways to provide the jumps address: - PC = operand - PC = PC + operand - PC = Z register (not an 8-bit register) JMP is a 32 bits command. It uses first way to provide the jumps address (PC=operand). The OP code is 10 bits long and 22 bits of operand, that are copied inside the program counter. The PC size depends on the microcontroller. Since a JMP is 32-bit command, we need 2 words to save it (2 address calls), so 2 machine cycles. RJMP is a 16-bit command. It uses the second way to provide jump address (PC = PC+operand). It is faster than JMP, since it uses only 1 machine cycle. 4 bits of OP code and 12 bits of operand, that are algebraically added to the PC. The operand is a signed number, as interpreted by the microcontroller. IJMP (Indirect Jump) has no operands. It assigns the content of the Z register to the PC. It is a 16- bit command, 16-bit of OP code. We have a lot of conditional jumps that jump to conditions that can be detected from the Status Register. Some of these jumps make operations on operands (like subtraction) in codes to modify the status register, but the result of the operation is not saved in register, like it happens in normal arithmetic operation. CP (Compare) works as a SUB, but it doesn’t change none of the Z registers. There are versions like CPC (Compute with Carry). Flags #Clocks Lesson 6 – 11 March Jump instructions are not a proper way to invoke subroutines because it’s difficult to come back to the position we want, if we go to the subroutine more than once. We need a more appropriate instruction. The stack is a data structure usually located at the end of the data address space. We can insert data in the stack with PUSH command, and extract with a POP. The stack has a Stack Pointer [SP] that points the first free cell. The stack pointer register is made of 2 8-bit registers. It’s located into the I/O address. SP: SPH | SPL LDI R16, HIGH (RAMENO) OUT SPH, R16 // primi 8 bit dell’indirizzo, quelli più significativi LDI R16, LOW (RAMENO) OUT SPL, R16 // ultimi 8 bit, quelli meno significativi HIGH and LOW are 2 macro that retrieve 8 bits, of a 16 bits number. RAMENO is the last location of the RAM memory and it depends on the microcontroller. When we set the SP register, we must always load first the high SPH and the SP. When we want to read, we have to do the opposite. We call a function with the command CALL. We return back with the command RET. The CALL stores the address of the command in the stack. The RET instruction retrieves then the address of the main flow from the top of the stack. The CALL is a 32-bit instruction but since it’s a complex instruction (it includes at least a PUSH instruction), it takes 4 or 5 machine cycles (depending on the size of the PC). The RCALL (Relative Call) is 16-bit, and it takes 1 machine cycle less than before. The PUSH is a 16-bit operation, executed in 2 machine cycles. The RET is another complex instruction. It may take 4 or 5 machine cycles, and it’s a 16-bit instruction. When we load 16 bits in a couple of 8-bits I/O registers, we always load first the high part, because it’s temporarily stored in a TEMP REG. When we load, after, a low part, contemporarily the value inside TEMP REG is loaded in the high register. Timers are special devices embedded in our microcontrollers that allow the system to wait for a given amount of time, so the CPU meanwhile can go ahead or do something else. Jump instructions are slower when pipelining is implemented in the CPU (but not with all jumps, mainly with conditional ones). This is called jump penalty or branch penalty. NOP is the no-operation command. It does nothing, it’s a 16-bit instruction that writes a machine cycle. Lesson 8 – 18 March We have 3 registers for each port. The number of the overall ports and the number of pins for each port depends from the microcontroller. The internal structure is composed by a circuit including together with the registers, come flip- flop, some transistor and the pull-up circuit, with the pull-up register. SBI (Set Bit in I/O Register) sets a bit without affecting the others on the same register. The bit becomes 1. On the contrary, CBI (Clear Bit in I/O Register) makes bit become 0. SBIC (Skip if bit in I/O Register Clear) SBIS (Skip if bit in I/O Register Set) Busy waiting is when the CPU keeps working doing the same thing until an event occurs. This is not a good way to use the CPU, because it wastes power. ADDC instruction performs an add including also the value of the carry flag. This is the correct way to do when we have to do with 16-bit big numbers. The same behaviour is done by SBC, for subtractions. MUL multiplies two unsigned numbers. MULS multiplies two signed numbers. MULSU multiplies a signed with an unsigned. The result is shared in R0 and R1. The lowest significative bits are in R0. We have no division commands, we have to do it manually. Lesson 9 – 24 March The preprocessor is able to perform simple operations on the operands, like arithmetic operations (+,-,*,/), or logical (& AND, | OR, > right shift). PER IL RESTO VEDI APPUNTI Lesson 10 – 25 March MACRO are very useful. They are like functions that accept parameters. When we use MACRO, the preprocessor will substitute it with its internal code. A macro ends with ENDMACRO. We can create a file full of macros and load it in our programs using the.INCLUDE directive. EEPROM is a place to store data that are not deleted when the power is off. ATMega32 has 1 KB of EEPROM. AVR architecture dedicates 3 registers to EEPROM: - EEARH:EEARL; //Addresses - EEDR; //Data - EECR; //Control Register Electrical Evaluable Programmable ROM The access time is greater than access time for RAM. We need 10 bits to map all the address, but the pair EEARH:EEARL offers 16 bits, so we have enough. EEARH only uses the 2 least significative bits available. EEPROM Data Register (EEDR) is used to load the data to store in the EEPROM, or to receive data that come from the EEPROM. EECR controls the direction of the transfer (read/write). We only use 4 bits: EERIE, EEMWE, EEWE and EERE. EEWE is the flag that tells is it’s possible to write. WE (Write Enabled). It’s useful because they might be concurrent. Same discourse for EERE (Read Enabled) Read is: - wait until EEWE becomes zero; - write new EEPROM address to EEARL; - set EERE to one; - read EEPROM data from EEDR Write is more complicated and it takes more time than reading - EEWE becomes zero; - write EEPROM address EEAR; - write data in EEDR; - set EEMWE to one; - within 4 dock cycles after setting EEMWE, set EEWE to one. The peripheral puts EEWE to 0 when the operation is finished. Lesson 11 – 31 March We can also program AVR with C programming language. It’s easier and faster to write and update respect to Assembly, but a much longer hex file. We have a lot of function libraries. The size of different data types is different: - unsigned char 8-bit 0 to 255 - char 8-bit -128 to 127 - unsigned int 16-bit 0 to 65535 - int 16-bit -32768 to 32767 - unsigned long 32-bit - long 32-bit - float 32-bit - double 32-bit Microcontrollers don’t have a standard input to receive parameters from, so the main() we write will always receive no parameter. If we want to modify single bits in a port, we have to use a bitmask and perform a logic operation, such as AND, OR and so on… The logic operations are &(AND), |(OR), ~(NEG) TIMERS AVR microcontrollers can also use timers and counters. The counter is a register. We can use a counter for different purposes: - delay; - counting; - wave-form generating; - capturing. We have from 1 to 6 timers in AVR, that can be 8-bit or 16-bit, two 8-bit timers and one 16-bit timer in ATmega32. The I/O name of the timers are TCNTn (Timer/Counter Register). The n is the number of the register. We have some flags in a specific register, like: - TOVn (Timer Overflow flag): it’s set to 1 when the time reaches the maximum capacity. - TCCRn (Timer Counter control register): it contains flag. - OCRn (output compare register): - OCFn (output compare match flag): set to 1 when the value of the counter matches the value of the comparator. Lesson 11 – 31 March The TOV flag must be reset manually. We calculate the delay generated by the timer knowing the frequency of the microcontroller and calculating the period of single time machine. Then we multiply the period for the number produced time machines. T = 1/f delay = T*k k = # of clock cycles In normal mode we can’t be very precise about timer calculation. The maximum delay is achievable putting 0x00 into TCNT. We can have longer delay with some expedients, like prescaling, looping or CTC mode (that uses OCR register). In CTC mode, differently from Normal Mode, we load the range to count in the OCR register. The timer status from 0 and, when the value reached inside the TCNT matches the one inside the OCR, the OCF flag becomes 1 and TCNT0 is reset to 0. Normal mode CTC mode In normal mode we have to set again the value that we want inside TCNT every time that it goes up to 0xFF. TOV0 and OCF0 are flags that don’t go back automatically to 0, so we have to manually reset them every time. We have different timers in an AVR architecture. Timer 0 and Timer 2 are 8-bit timers, while Timer 1 is 16-bit. Timer 1 is a 16-bit timer, so it’s more complex to set (we have TCNT1H and TCNT1L). As usual, we always set the high register. It is connected to 2 different OCR registers that are 16-bit too. We can see that in TIFR register we have OCF1A and OCF1B. We also have 2 TCCR, that are TCCR1A and TCCR1B. 16-bit registers could have an only stored TEMP register that preload the high part of the value. We should look for the architecture our specific microcontroller. Lesson 15 – 14 April ADC (Analog to Digital Conversion). This converter is used to converA2D. In TTL Logic we connect ranges of voltage that goes from 5V to 4V and 0 from 1V to 0V. A potentimeter is like a resistance that can dynamically change its value within a range. In a given range, for example from 0V to 12V we can have infinite analog values. How can we store numberof an infinite set? We can save a limited amount of data with bits. With 8 bits we can save only 256 different values. If we have n bits, we can divide our analog range in 2n equal length portions (quantization). Unfortunately, we lose accuracy and duality in this conversion. AVR microcontrollers are equipped with ADC, some of them also with DAC (Digital to Analog Converter). ADC converter is needed to elaborate data that come from sensors. An ADC usually works with a Voltage Reference (Vref), in order to adjust intervals based on it. Internal ADC in AVR can choose between 3 different Vref, one of them is internally provided and has the value of 2.56V. The Successive Approximation Register (SAR) stores a value that starts with 1000 0000 and understands an input voltage, converting it to binary. It changes bits 1 by 1 until it reaches the correct input value. Knowing the quantization and the value of each step (10 mV is Vref is 2.56V). 1000 0000 corresponds to 1.28V, so if we have to create the conversion to 1V, we need a lower value, so the 7th bit is changed and the new step status from 0100 0000, that corresponds to 0.64V. The 6th bit is kept and the next step status from 0110 0000, and so on, clearing or setting the bit until we reach the least significative bit. ADCH:ADCL store the results of the conversion. The 10 bits can be right or left justified setting the ADLAR flag (0 is right and 1 is left). ADMUX sets up the voltage reference for the ADC and also contains ADLAR and flags for channel section. ADCSRA register contains flag that control the ADC. We can change the clock frequency of the ADC with a Prescaler. The frequency of ADC shouldn’t be more than 200 KHz, so we have to reduce it in comparison with CPU clock. Programming ADC Lesson 16 – 15 April Vedi appunti per transducer prima di questo SERIAL PORT PROGRAMMING There is a difference between parallel and serial communication. Parallel transfers a byte of data at a time (faster, easier). Serial transfers a bit after another, is ideal for long distance and generally is cheaper. The direction can be in 3 ways: - simplex when data can moves only in one direction - half duplex, data can moves in two directions but not at the same time - full duplex, data can moves in two directions at the same time, because there are 2 separated lines. Serial communication can be: - synchronus, clock pulse should be transmitted during data tansmission. Only one side generates clock at the same time - asynchronus, clock pulse is not transmitted, the two side should generate clock pulse. There shoulde be a way to synchronize the two sides. (they need an agreement) A technique to synchronize is framing. A frame starts with a 0 bit, also called space. The start bit is followed by a character that can be 7 or 9 bits long. After the character we can optionally have a parity bit. The parity bit is used to check for errors. Each frame is ended by one or two marks (1) that are called stop bit(s). bps is bits per second, and this is the clock frequency that is agreed. RS232 is a standard set in 1960, but its input and output voltage level are not compatible with TTL logic family. DB9 is an implementation of this standard. Previously we also had DB25, with 25 pins, very huge. These are the pins of DB9: AVR supports both synchronus and asynchronus communications. 5 registers are associated with USART: - UBRR, USART Baud Rate Register - UDR, USART Data Register - UCSRA, USART Control and Status Register A - UCSRB, USART Control and Status Register B - UCSRC, USART Control and Status Register C UBBR is used to let two sides agree on a baud rate. It is 16 bit register. Baud rate is the number of character that we tranfer in a second. UDR is a bridge between the microcontroller and the serial shift register. If we read UDR, data is read from received shift register. If we write UDR, data is written into transmit shift register. UCSRA UCSRB UCSRC STEPS SERIAL PORT PROGRAMMING APPUNTI Lesson 18 – 21 April Other integrated circuit are needed when our microcontroller that works in a range of voltage values must be connected to a peripheral that works within a different ranges like DB9. We don’t need the intermediate circuit when we use the USB port. For serial communication we prefer to use libraries to semplify the code. Wave Form Generator are connected to timers. Timer 1 has 2 waveform generators. We use WGM flags inside the TCCRx register. COM (Compute Output Mode) determines what is written in the output port. Lesson 19 – 22 April If we change the duty cycle of the wave form, we can have different high intensity. d=TH/TH+TL What happen if we connect a DC motor to a wave form? VEDI APPUNTI FABRIZIO Lesson 21 – 28 April SPI – SERIAL PERIPHERAL INTERFACE This protocol is used to let different devices communicate. Many devices can be connected together through a bus. SPI uses 2 pins for data transmission (SDI and SDO) and one for clock (SCLK) and a chip enable (CE). The pins are alternatively named MISO (Master In Slave Out), MOSI (Master Out Slave In), SS (Slave Select) and SCK. Master and slave have, internally, SPI Shift Register that are connected each other via the two MISO and MOSI channels. SS is used to enable the SPI peripheral (usually controlled by the master). At each clock cycle the LSB of master register is sent via MOSI to the MSB of the slave register, and contemporaney the LSB of the slave register is sent via MISO to the MSB of the master slave register. We have a full- duplex communication. If we have many slaves, we can use a multiplexer to select the specific one. In SPI, master and slave must agree on clock polarity phase. Clock Polarity (CPOL) is the level of the wave form (0 or 1). CPHA (Clock Phase) SPSR is SPI Status Register SPIF (SPI Interrupt Flag): Transmitted/Received Switched to Slave mode WCOL (Write COLision Flag) SPI2X (Double SPI Speed bit): 1 is double SPCR (SPI Counter Register) SPIE (SPI Interrupt Enable) SPE (SPI Enable) DORD (Data Order): 1:Send LSB 1st, 0:Send MSB 1st MSTR (Master/Slave Select): 1:Master, 0: Slave CPOL (Clock Polarity): 1: idle SCK = 1, 0: SCK = 0 CPHA (Clock Phase): SPR1 (SPI Clock Rate Select 1) SPR0 (SPI Clock Rate Select 0) SPRx and SPI2x select the clock speed. In Master Mode out microcontroller can choose various parameters, like data order and direction. If we set direction to outuput, the SS pin will not be controlled, while in input direction it can be controlled. If we set SS to input, then it should be externally pulled up. If we make it pulled down, the SPI module will start working in Slave mode, generating an interrupt. In Slave Mode SS pin is always input and we can’t control it by software. We should activate it low externally to activate the SPI. If SS pin is driven high, SPI is disabled and all pins of SPI are input. Also the SPI module will immediately clear any partially received data in the shift register BUT IT WILL NOT BE DISABLED. Let see Master Operating Mode and how can configure our microcontroller: Set the MSTR bit to one Set SCK frequency by setting the values of SPI2X, SPR1, and SPR0 Set the SPI mode. If not set, it is 0. Enable SPI by setting the SPIE bit to one Write a byte to the SPI Data Register (SPDR) Poll the SPIF flag. Data transfer is finished when it changes to one. read the received byte from SPDR before the next byte arrives. o Note: After the transmission, the byte in the Master shift register is moved to the Slave Shift register and the Byte in the Slave shift register is moved to the Master shift register. It means that send and received happens at the same time. If you only want to read a byte, you should transmit a dummy byte like 0xff and then read the received data! Slave Operating Mode Set the SPI mode. If not set, it is 0. Enable SPI by setting the SPIE bit to one Write a byte to the SPI Data Register (SPDR) Poll the SPIF flag. Data transfer is finished when it changes to one. read the received byte from SPDR. 7-Segments are made of 7 LEDs that show different numbers plus another LED to display decimal point. There are two types of 7-segments, common anode and common cathode. MAX 7921 (SPI 7-Seg Driver) receives a 16-bit command: The first byte (MSBs) are the command control bits o (D15–D12) of the command byte are don’t care o (D11–D8) are used to identify the meaning of the data byte to be followed The second byte (D7–D0) of the two-byte packet is called the data byte 14 different commands Set decoding mode (command X9) This command lets you enable or bypass the binary to 7-segment decoding function for each 7- segment. If you want to enable the decoding function for a digit you should set to one the bit assigned to that digit, and if you want to disable the decoding function you should clear the bit for that digit. D0 is assigned to Digit 0, D1 is assigned to Digit 1, and so on. If decoding mode is off Use the “Set value of digit x” command to turn on/off each bit of a segment. If you want to turn on a segment, you should write one to its bit. If you want to turn off a segment, you should write zero to its bit. Each bit of the data bits is assigned to a segment of the segment. If decoding mode is on Use the “Set value of digit x” command and just send the BCD value of the number to be displayed on the 7-Segs. The following figure shows how to use command 3 ( set the value of digit 2 , the 3rd digit) to 5 (101) MAX7221 programming in AVR 1. Initialize the SPI to operate in master mode 0. 2. Enable or disable decoding mode by executing command 9 (x9 hex). 3. Set the scan limit. (default is 0 ! ) 4. Set the intensity of light (optional). 5. Turn on the display. 6. Set the value of each digit. Lesson 22 – 29 April I2C Inter-Integrated Circuit It’s another serial syncronus communication protocol, TWI (Two Wire Interface) because it only needs two wires to communicate, SDA (Serial Data) and SCL (Serial Clock). About 128 devices (nodes) can share a common bus. If only one of the devices write 0 on the SDA, every other devices will read 0. It’s called Wire AND. Same thing happens on SCL line. Master device transmits CLK, regardless if it’s working as transmitter or receiver. The receiver doesn’t change the level of SDA unless it wants to generate ACK. Transmissiton is half-duplex Both master and slave can play the role of transmitter and receiver. Each data bit is transferred on SDA and is syncronized by a falling edge of clock on SCL line. Everytime the clock is low, data can change its bit. STOP and START conditions are exceptions. They must be distinguished by the rest of data. START is a high-to-low during a high on SCL. STOP is a low-to-high during a high on SCL. Data are transmitted in sequence of packets. Each packet is 9 bits long. First 8 bits are put on SDA by the transmitter and the 9th is an acknowledge (ACK) of the receivere. ACK is 0, NACK is 1. There are 2 types of packets: Address Packet Data Packet Address packet is used to choose the receiver of the data. Each device connected is given a 7-bit address. In the 8th bit of of Address Packet we define the R/W mode. Address is sent from MSB to LSB. We first send the address, then R/W, then we wait for ACK. If R/W is 1, we act as receiver, while 0 is will to transmit. Data packet can be transmitted both by master and slave. 8 bit long (MSB first). Is followed by ACK/NACK from receiver. NACK not necessarily indicate error. Typical data transmission Data address packet can be used only by masters. Some addresses are reserved, like 0000 000, reserved for general call. All addresses that are 1111 xxx are reserved, so we actually have 119 availabe addresses (128 - 1 – 8). Clock Stretching is a form of flow control, to let the master indicate that he’s going too fast. Slave tries to pullSCL low. Master waits for SCL line to become high. When Slave is ready again, SCL is released again and master can start again. If an addressed slave device is not ready to process more data it will stretch the clock by holding the clock line (SCL) low after receiving (or sending) a bit of data. Arbitration is due to the fact that I2C supports multi-masters. Anyway, onle one master can use the bus at the time. If 2 or more masters initiate transmission at the same time, we must choose only one of them. Each transmitter has to check the value of the SDA bus with the one that is expected. If they don’t match, then the transmitter can’t be the master and becomes a slave, because there was a collision. When 2 masters start together, the 1st one that writes a 0 and the other writes a 1, it gets the privilege to be the master and the other becomes a slave. Multibyte Burst write is used to load data in consecutive locations. We only have to transmit the address of the first location, soon after the address of the sender. Same thing happens in multibyte burst read. I2C in AVR In AVR we have registers that control the I2C. We have the TWSR (TWI Status Register). The 2 least significative bits control the precedence. It is inside the Control Unit, together with the TWCR (Control Register). TWI Status Register (TWSR) TWBR (TWI Bit Rate Register), located inside the Bit Rate Generator. TWAR (TWI Address Register) Inside is specified the address of the microprocessor that we’re using as slave. TWAR is divided in 2 potions: the 7 MSB detect the slave address, while the LSB is a flag called TWGCE (TWI General Call Recognition Enable bit) that allows to answer to general calls. TWI Control Register TWINT (TWI Interrupt Flag) is the interrupt flag. TWEA (TWI Enable Acknowledgement Bit): 1 ACK, 0 NACK TWSTA (TWI Start Condition Bit) TWSTO (TWI Stop Condition Bit) TWCC (TWI Write Collision flag) TWEN (TWI Enable bit): enables the peripherals TWIEN (TWI Interrupt Enable): enables interrupts TWI, Master Mode Programming In order to let work our microcontroller in master mode, we have to perform these steps: 1. Initialization. Set the TWI module clock frequency by setting the values of the TWBR register and the TWPS bits in TWSR register. Then set TWEN bit in TWCR to one to enable the TWI module. 2. Transmit start condition. We set TWEN, TWSTA and TWINT bits of TWCR to one. 3. Send data. Copy the data byte to the TWDR, then set the TWEN and TWINT bits of TWCR to one to start sending the byte. Poll TWINT flag in TWCR register to see whether the byte is transmitted completely. 4. Receive data. We set TWEN and TWINT bits of TWCR to one to start receiving a byte, then we poll TWINT flag in TWCR to see whether a byte has been received completely. We finally read the received byte from the TWDR. 5. Transmit STOP condition. Set TWEN, TWSTO and TWING bits of TWCR to one. We cannot poll the TWINT flag after transmitting the STOP condition. TWI, Slave Mode Programming As regarding programming in slave mode, these are the steps: 1. Initialization. Set the slave address by setting the values for the TWAR register. a. 7 bits for address b. 8th bit is TWGCE (1 = answer general calls). Set the TWEN bit in TWCR to one to enable the TWI module, then set the TWEN, TWINT and TWEA bits of TWCR to one to enable the TWI and acknowledge generation. 2. Listening. Poll the TWINT flag to see when the slave is addressed by a master device or use its interrupt. 3. Send Data. Copy the data byte to the TWDR, then set the TWEN, TWEA and TWINT bits of TWCR register to one to start sending the byte. Poll TWINT flag in TWCR register to see whether the byte is transmitted completely. 4. Receive Data. We set TWEN and TWINT bits of TWCR to one to start receiving a byte, then we poll TWINT flag in TWCR to see whether a byte has been received completely. We finally read the received byte from the TWDR. We can connect on RTC (Real Time Counter) with I2C to our microcontroller. Lesson 24 – 5 MAY FREERTOS It’s a real-time OS built for microcontrollers. It’s mainly written in C programming language. It’s a multitask system with a single processor. Each time is assigned a certain amount of time to be executed. The kernel schedules the various available tasks, exploiting CPU to achieve concurrency. We have to introduce a scheduling policy, that takes into account dependencies among tasks and priorities. Usually, tasks are event-driven, but there are also periodic tasks that have to satisfy time constraints. A real-time OS must satisfy these tasks, otherwise the system may fail. Tasks are implemented as a C function. They must return void and take a void pointer parameter. This is the required prototype. The pointer has the size of an address. A void pointer can be interpreted as a pointer to an unspecified type of data. It’s flexible. Task functions must not contain any return statement, but they have to invoke a specific function of the OS (vTaskDelete). Calling this function with NULL parameter means that the function to be deleted is the calling one. Tasks can have different states: running and not running. They change the state with transitions. After functions are made, we declare then as tasks by the function xTaskCreate(). It has 6 parameters: 1. pvTaskCode is a pointer to a function (the one that implements the task); 2. pcName is a descriptive name for the task, to help you debug among others. Only for debug purposes. The size of the name is limited, the maximum length of a task’s name is set using the configMAX_TAKS_NAME_LEN parameter in FreeRTOSConfig.h 3. usStackDepth is the size of a stack to be allocated in the task, store its configuration and register value when not running. The size is the number of words. The size of the words depends from the system. We have a minimal stack size. 4. pvParameters, a value that will be passed to our task entry function. If you don’t use it set t to NULL as recommended by FreeRTOS. 5. uxPriority: like on any operating system, threads have priority. In case two tasks want to run simultaneously, the one with the higher priority will take the preference. Will set both of our task to the lowest priority, tskIDLE_PRIORITY. 0 to MAX_PRIORITIES-1 are available values. 6. pxCreatedTask: is used to manage task handles, a way to control the tasks. If don’t need to use it, set to NULL. Most of the tasks are implemented with an infinite loop. After creating tasks, the schedule starts when we call vTaskStartScheduler. vTaskStartScheduler never finishes, so that from this point only our two tasks will be running. Tasks with equal priority at runtime are executed in a round-robin way. Tasks can change their priority at runtime with vTaskPrioritySet(). Tasks with different priorities are assigned with different time slices. Lesson 24 – 6 May We can use simulators to run FreeRTOS. In C, a volatile variable is not subject to optimization. For example, a compiler may perform an optimization by eliminating for loops that have nothing inside. If this for loop is driven by a volatile variable, then the compiler cannot remove it. We actually have more than 2 states. An expanded state diagram also contains these three states that substitute the Not Running State: - Suspended - Ready - Blocked Is it’s ready, it’s available to be run. If it’s Blocked, it’s waiting for an event. There are 2 main events to be waited: - Temporal events, in order to perform tasks at given intervals. - Synchronization events, when it’s waiting from some data or for another task. If the task is Suspended, then it declares itself to be not available to the scheduler. Tasks are suspended by the call vTaskSuspended(). One of the most used API that block a task is the vTaskDelay(), that receives, as parameters, the number of ticks to wait. This call is not a perfect periodic, because there are other instructions in the task that use clock cycles. By using vTaskDelayUntil(), instead, we have a perfect period. We specify the tick count. When we want to perform a task at a fixed periodic frequence, this is the call that we have to use. It receives 2 parameters: - a pointer to the previous wake time (the time the task has left the blocked state) - the time increment. The pointer to previous wake time is automatically updated, we retrieve the value with xTaskGetTickCount(). The idle task has the lowest possible priority. It’s used to perform background operations when no other tasks are running. It receives just a void as a parameter. It is called vApplicationIdleHook(). In order to use it, we have to define it in the config file, by enabling VSE_IDLE_HOOK. We can change a priority of a task with the API function vTaskPrioritySet(). The function itself can call this API call. Using null as parameter refers to the same calling function. We can inspect priority of a task by using vTaskPriorityGet(). The scheduler always schedules the task with the highest priority in the ready state. It’s called Fixed Priority Preemptive Scheduling. Each task has its own stack are, that is freed when the task is deleted. We can assign priorities to tasks with the RMT (Rate Monotonic Technique). FreeRTOS can also use another kind of scheduling that is called co-operative scheduling, where tasks are not preemptive. QUEUE MANAGEMENT Tasks can communicate each other by sharing a memory area to read and write data, but this solution has concurrency risks. A better way is to use QUEUES. A queue has a limited capacity, and we can specify the size. With queues we can implement asynchronous communication. Normally, queues are FIFO, but is also possible to write in the front of the row. Reading a value from a queue removes it from the queue. Queues can be used by any task. They act as a bounded buffer. If a task wants to read from a queue, and the queue is empty, the task is blocked and it may be awaked when some other task writes into it. A task can also have a block time, that is the max amount that he wants to wait. If there are many tasks waiting on a queue, the one that is awaked is the one with the highest priority, otherwise the one that is waiting for the longest time. If a task wants to write but the queue is full, then it is also blocked in a waiting situation. We create queues with xQueueCreate(). We can write with xQueueSendtoBack() or xQueueSendtoFront(). We should never use these calls from an ISR, but we should use, in this case, xQueueSendtoBackFromISR() and xQueueSendtoFrontFromISR(). With xQueueMessagesWaiting we can see the number of waiting.