AVR Programming: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | The FPC commands for programming AVR microcontrollers such as ATMega or ATTiny are the same commands as always, but because of its character of an embedded system, i.e. no underlaying operating system and direct hardware access, there are several specific topics to know. | |
| 2 | 2 | ||
| 3 | 3 | For building the FPC compiler see article [[AVR]]. | |
| 4 | 4 | ||
| 5 | 5 | ## LCL/FCL | |
| 6 | 6 | ||
| 7 | 7 | LCL is not available and FCL only partially. | |
| 8 | 8 | ||
| 9 | 9 | ## Variables | |
| 10 | 10 | ||
| 11 | 11 | Variables declaration etc. works in the same way as always. As there is no operating system which provides memory allocation, dynamic types like AnsiString could not be used. | |
| 12 | 12 | ||
| 13 | 13 | ### Integer variables | |
| 14 | 14 | ||
| 15 | 15 | The AVR is a 8 bit architecture, the native datatypes are *[[Byte]]* (= *uInt8*) with a range from 0 to 255 and *[[Shortint]]* (= *Int8*) ranging from -128 to 127. Calculations and operations with these two datatypes are always the fastest. A difference in comparison to e.g. target win32 is that the *Integer* type has 16 bit with range between -32768 and 32767 (instead of 32bit for win32). Using the integer type will increase the calculation time in comparison to the native types, but may be reasonable depending on the application. For loops with iteration of less than 256 cycles the iteration variable with type byte is appropriate. | |
| 16 | 16 | ||
| 17 | 17 | For clarity reasons if not using Byte or Integer, the type names Int8, uInt8, Int16, uInt16, Int32, uInt32, Int64 and uInt64 may be prefered. | |
| 18 | 18 | ||
| 19 | 19 | ### Floating point variables | |
| 20 | 20 | ||
| 21 | 21 | The AVR architecture doesn’t have a floating point unit. Not sure if using soft float is possible by now. | |
| 22 | 22 | ||
| 23 | 23 | ### String variables | |
| 24 | 24 | ||
| 25 | 25 | Only strings with fixed length can be used, as AnsiString and WideString types are dynamically allocated. | |
| 26 | 26 | ||
| 27 | 27 | Some Examples: | |
| 28 | 28 | ||
| 29 | 29 | ``` pascal | |
| 30 | 30 | // passing a string as a procedure variable | |
| 31 | 31 | procedure UARTSend(const AText: ShortString); | |
| 32 | 32 | // using a variable with fixed length string | |
| 33 | 33 | var myString: String[5]; | |
| 34 | 34 | ``` | |
| 35 | 35 | ||
| 36 | 36 | > **Note:** The string type ShortString is always 255 chars in length. If you need fewer characters, use a string with fixed length to not waste the scarce memory. Use ShortString only if you don't know how long your string may be at maximum length. | |
| 37 | 37 | ||
| 38 | 38 | ## Accessing hardware configuration registers | |
| 39 | 39 | ||
| 40 | 40 | All hardware configuration registers are defined in the controler specific configuration file which is located in the pascal sources under … \rtl\embedded\avr. The configuration file is named after the device, e.g. Atmega32.pp for the device Atmega32. Besides the configuration registers it also contains the predefined interrupt names. The file is included automatically for all units, so the register variables and constants are available in each unit. The device and thus the configuration file is defined by the command line parameter –wp*devicename* (e.g. –wpatmega32). | |
| 41 | 41 | ||
| 42 | 42 | The names of the hardware registers correspond to the register names defined in the device datasheet. The hardware registers can be accessed as easy as variables. E.g. \<syntaxhighlight lang="pascal\>UDR:= 25; | |
| 43 | 43 | ||
| 44 | - | ||
| 45 | - | ||
| 46 | 44 | writes the value 25 in the UARTDataRegister. Bit manipulation can effectively be done with shift operations ([[Shl|shl]] = „shift left“), e.g. | |
| 47 | 45 | ||
| 48 | 46 | ``` pascal | |
| 49 | 47 | UCSRB:= (1 shl TXEN) or (1 shl RXEN) or (1 shl RXCIE); | |
| 50 | 48 | UCSRB:= (1 shl 3) or (1 shl 4) or (1 shl 7); | |
| 51 | 49 | UCSRB:= %10011000; //Bit 3,4 and 7 are set | |
| 52 | 50 | ``` | |
| 53 | 51 | ||
| 54 | 52 | All three lines are identical in the generated code: The bits TXEN, RXEN and RXCIE are set in the „USART Control and Status Register B“, thus the UART hardware is enabled for sending and receiving and for interrupts on receiving. | |
| 55 | 53 | ||
| 56 | 54 | ## Register name aliases | |
| 57 | 55 | ||
| 58 | 56 | For hardware abstraction purpose its common to define application specific names for hardware configuration registers, especially for port registers. This can be done by a variable definition using the absolute modifier. In the below example LEDPort is just an other name for the hardware register PortA, such definitions are neutral in memory consumption and performance. | |
| 59 | 57 | ||
| 60 | 58 | ``` pascal | |
| 61 | 59 | var | |
| 62 | 60 | LEDPort: byte absolute PortA; | |
| 63 | 61 | const | |
| 64 | 62 | LEDPin = 3; | |
| 65 | 63 | ||
| 66 | 64 | implementation | |
| 67 | 65 | LEDPort:= LEDPort or (1 shl LEDPin); //LED=on | |
| 68 | 66 | ``` | |
| 69 | 67 | ||
| 70 | 68 | ## Bit arithmetic | |
| 71 | 69 | ||
| 72 | 70 | Bit arithmetic is the Free Pascal standard, but as it is especially important for an embedded target and quite rarely used in desktop applications it is mentioned here. Available operators for bit manipulation: [[Not]], [[And]], [[Or]], [[Xor]], [[Shl]] and [[Shr]]. | |
| 73 | 71 | ||
| 74 | 72 | Example: | |
| 75 | 73 | ||
| 76 | 74 | ``` pascal | |
| 77 | 75 | ByteVal1:= (ByteVal1 and %11110000) or %00001000; | |
| 78 | 76 | ``` | |
| 79 | 77 | ||
| 80 | 78 | A leading ‚[[Percent sign|%]]’ indicates a [[Binary numeral system|binary number]], a leading ‚[[Dollar sign|$]]’ a [[Hexadecimal|hexadecimal number]]. For inline assembler ‚0b’ and ‚0x’ are the indicators respectively. | |
| 81 | 79 | ||
| 82 | 80 | ## Classes/objects | |
| 83 | 81 | ||
| 84 | 82 | Again, classes are dynamically allocated, and thus are not available. But static classes (keyword *object*) can be used for structuring and reusing code. | |
| 85 | 83 | ||
| 86 | 84 | Example: | |
| 87 | 85 | ||
| 88 | 86 | ``` pascal | |
| 89 | 87 | Type TUART = object | |
| 90 | 88 | Procedure Init; | |
| 91 | 89 | Procedure Send(ByteVal:Byte); | |
| 92 | 90 | end; | |
| 93 | 91 | ||
| 94 | 92 | var | |
| 95 | 93 | UART: TUART; | |
| 96 | 94 | ``` | |
| 97 | 95 | ||
| 98 | 96 | Usage: | |
| 99 | 97 | ||
| 100 | 98 | ``` pascal | |
| 101 | 99 | UART.Init; | |
| 102 | 100 | UART.Send(125); | |
| 103 | 101 | ``` | |
| 104 | 102 | ||
| 105 | 103 | ## Interrupts | |
| 106 | 104 | ||
| 107 | 105 | When using hardware interrupts one has to configure and enable a interrupt source, enable interrupts globally and define an interrupt service routine (ISR) which is called by the CPU when the interrupt occurs. The configuration is done via the hardware registers. For globally enabling interrupts the assembler command SEI is available, it can be easily used with inline assembler: | |
| 108 | 106 | ||
| 109 | 107 | ``` pascal | |
| 110 | 108 | asm SEI end; | |
| 111 | 109 | ``` | |
| 112 | 110 | ||
| 113 | 111 | Globally disabling interrupts is done with the assembler command CLI: | |
| 114 | 112 | ||
| 115 | 113 | ``` pascal | |
| 116 | 114 | asm CLI end; | |
| 117 | 115 | ``` | |
| 118 | 116 | ||
| 119 | 117 | **Note:** Since FPC 3.2.0 one can also clear/set interrupts without resorting to inline assembler: | |
| 120 | 118 | ||
| 121 | 119 | ``` pascal | |
| 122 | 120 | uses | |
| 123 | 121 | intrinsics; | |
| 124 | 122 | ||
| 125 | 123 | procedure unInterruptableCode; | |
| 126 | 124 | begin | |
| 127 | 125 | avr_cli; | |
| 128 | 126 | // code that shouldn't be interrupted | |
| 129 | 127 | avr_sei; | |
| 130 | 128 | end; | |
| 131 | 129 | ``` | |
| 132 | 130 | ||
| 133 | 131 | The code above may introduce the side effect of enabling interrupts, even if disabled initially. To fix this, call avr_save to save the status register (it also disables interrupts), perform uninterrupted actions, then restore status register: | |
| 134 | 132 | ||
| 135 | 133 | ``` pascal | |
| 136 | 134 | uses | |
| 137 | 135 | intrinsics; | |
| 138 | 136 | ||
| 139 | 137 | procedure unInterruptableCodeImproved; | |
| 140 | 138 | var | |
| 141 | 139 | prevState: byte; | |
| 142 | 140 | begin | |
| 143 | 141 | prevState := avr_save; | |
| 144 | 142 | // code that shouldn't be interrupted | |
| 145 | 143 | avr_restore(prevState); | |
| 146 | 144 | end; | |
| 147 | 145 | ``` | |
| 148 | 146 | ||
| 149 | 147 | For definition of the ISRs, predefined names have to be used. Depending on the device, different interrupts are available. The proper names can be found in the device specific unit (e.g. „Atmega32.pp“) and have to be used in the *Alias* modifier. A working ISR looks as follows (example: External Interrupt 0): | |
| 150 | 148 | ||
| 151 | 149 | ``` pascal | |
| 152 | 150 | procedure ExternalInterrupt0_ISR; Alias: 'INT0_ISR'; Interrupt; Public; | |
| 153 | 151 | begin | |
| 154 | 152 | //Do some stuff… | |
| 155 | 153 | end; | |
| 156 | 154 | ``` | |
| 157 | 155 | ||
| 158 | 156 | The procedure name itself can be chosen randomly, important is the name behind *Alias*. The *Alias* modifier forces the compiler to use the name *INT0_ISR* for the procedure without name mangling. If this name fits a predefined interrupt name, then the procedure will be automatically registered in the interrupt vector table. For the automatism to be possible, the procedure has to be globally visible, which is realised by the Public modifier. But this is only needed if the procedure is defined in a unit. Note: There is no procedure prototype to be defined in the INTERFACE section of the unit, but only the bare procedure in the IMPLEMENTATION section. If the procedure is placed in the program section instead of inside a unit, then the Public modifier is omitted. The Interrupt modifier makes sure that all registers and the status register are saved and recovered by the ISR and that interrupts are further enabled. Omitting the Interrupt modifier will result in the malfunction of and crashes of the program. | |
| 159 | 157 | ||
| 160 | 158 | ## Inline assembler | |
| 161 | 159 | ||
| 162 | 160 | In some cases performance can be highly increased by a few assembler statements. The inline assembler provides a very convenient way of mixing Free Pascal and assembler. | |
| 163 | 161 | ||
| 164 | 162 | ### Inline asm block | |
| 165 | 163 | ||
| 166 | 164 | The [[Asm|ASM statement]] starts an assembler block which is finished with an END statement. | |
| 167 | 165 | ||
| 168 | 166 | ``` pascal | |
| 169 | 167 | asm | |
| 170 | 168 | // do some assembly | |
| 171 | 169 | end; | |
| 172 | 170 | ``` | |
| 173 | 171 | ||
| 174 | 172 | In the assembler block modified registers have to be published to the compiler in the end statement, so that the compiler can take care of pushing or avoiding registers. The resulting code is more effective than pushing registers manually inside the asm section. Modified but unpublished registers may lead to malfunction. | |
| 175 | 173 | ||
| 176 | 174 | ``` pascal | |
| 177 | 175 | asm | |
| 178 | 176 | ldi r20,35 // r20 modified | |
| 179 | 177 | ldi r21,12 // r21 modified | |
| 180 | 178 | end['r20','r21']; // publish modified registers to compiler | |
| 181 | 179 | ``` | |
| 182 | 180 | ||
| 183 | 181 | The register r1 is assumed by the compiler to always be zero. If modified, e.g. by a *mul* operation, it must be cleared afterwards. | |
| 184 | 182 | ||
| 185 | 183 | ``` pascal | |
| 186 | 184 | mul r16,r17 // result stored in r1 and r0 | |
| 187 | 185 | movw r1:r0,r17:r16 | |
| 188 | 186 | clr r1 // clear r1 | |
| 189 | 187 | ``` | |
| 190 | 188 | ||
| 191 | 189 | Constants and variables can be accessed within the inline assembler. The variables have to be distinguished between local variables (stack allocated), which are loaded and stored with the instructions LDD and STD (limited to 64 byte offset), and global variables (statically allocated), which are loaded and stored with the instructions LDS and STS. Variables passed by procedures are allocated on the stack and thus are handled like local variables. Its always useful to check the assembler listing (.s-file extension). | |
| 192 | 190 | ||
| 193 | 191 | When using jumps in the assembly then labels are used. They have to be declared in the label section of the procedure. Allowing this, the compiler directive *{\$goto on}* is mandatory. | |
| 194 | 192 | ||
| 195 | 193 | Note: The peripheral configuration registers defined in the controller-specific file (e.g. ‚atmega32.pp’) are variables with addresses in the memory address space and NOT in the I/O-space. There the addresses are shifted by 0x20, as the general registers are the bottom of the address space. Thus I/O assembler commands like **in**, **out**, **sbis**, **sbi** (and so on) doesn’t work with the register names. Compare with the memory map in the controller datasheet. | |
| 196 | 194 | ||
| 197 | 195 | Instead of **in**/**out** the commands **lds** and **sts** can be used: | |
| 198 | 196 | ||
| 199 | 197 | ``` pascal | |
| 200 | 198 | lds r16, PORTB //Reads PORTB into register r16 | |
| 201 | 199 | ``` | |
| 202 | 200 | ||
| 203 | 201 | The bit definitions of the perpheral configuration registers can be used in the inline assembler. | |
| 204 | 202 | ||
| 205 | 203 | If I/O assembler commands are required, the I/O register addresses can be shifted manually by 32 (=0x20): | |
| 206 | 204 | ||
| 207 | 205 | ``` pascal | |
| 208 | 206 | //Wait until UART data register is empty | |
| 209 | 207 | Procedure UARTBusyWait;assembler; | |
| 210 | 208 | asm | |
| 211 | 209 | .waitBusy: | |
| 212 | 210 | sbis UCSRA+(-32),UDRE | |
| 213 | 211 | rjmp .waitBusy | |
| 214 | 212 | end; | |
| 215 | 213 | ``` | |
| 216 | 214 | ||
| 217 | 215 | In the following an example for a delay function with inline assembler, which pauses the program for a certain time making the CPU busy. | |
| 218 | 216 | ||
| 219 | 217 | ``` pascal | |
| 220 | 218 | unit Wait; //wait.pas | |
| 221 | 219 | {$mode objfpc}{$H+} | |
| 222 | 220 | {$goto on} //Important for using the label directive, the label is needed in the assembler section | |
| 223 | 221 | ||
| 224 | 222 | INTERFACE | |
| 225 | 223 | ||
| 226 | 224 | Const | |
| 227 | 225 | f_CPU = 4000000; //Hertz | |
| 228 | 226 | ||
| 229 | 227 | Procedure Wait10m(Time: Byte); | |
| 230 | 228 | ||
| 231 | 229 | IMPLEMENTATION | |
| 232 | 230 | ||
| 233 | 231 | // Waitingtime = Time * 10 Milliseconds | |
| 234 | 232 | Procedure Wait10m(Time: Byte); | |
| 235 | 233 | const | |
| 236 | 234 | Faktor = 15 * f_CPU div 1000000; | |
| 237 | 235 | label // Labels, here for the loop, has to be declared explicitly | |
| 238 | 236 | outer, inner1, inner2; | |
| 239 | 237 | var | |
| 240 | 238 | tmpByte: byte; // In Inline assembler local variables are accesed by the instructions LDD and STD, global variables are accessed by LDS and STS. | |
| 241 | 239 | begin | |
| 242 | 240 | asm // asm states inline assembly block until the next END statement | |
| 243 | 241 | ldd r20,Time // Variables can be accessed, here a local variable | |
| 244 | 242 | outer: | |
| 245 | 243 | ldi r21, Faktor | |
| 246 | 244 | inner1: // 640*Faktor= 640*15*1 = 9600 cycles/1MHz | |
| 247 | 245 | ldi r22,128 | |
| 248 | 246 | inner2: // 5*128 = 640 cycles | |
| 249 | 247 | nop // 1 cycle | |
| 250 | 248 | nop // 1 cycle | |
| 251 | 249 | dec r22 // 1 cycle | |
| 252 | 250 | brne inner2 // 2 cycles in case of branch //one loop in sum 5 cycles | |
| 253 | 251 | dec r21 | |
| 254 | 252 | brne inner1 | |
| 255 | 253 | dec r20 | |
| 256 | 254 | brne outer | |
| 257 | 255 | end['r20','r21','r22']; //Used registers to be published to compiler | |
| 258 | 256 | end; //procedure | |
| 259 | 257 | ||
| 260 | 258 | end. //unit | |
| 261 | 259 | ``` | |
| 262 | 260 | ||
| 263 | 261 | ### Asm procedure | |
| 264 | 262 | ||
| 265 | 263 | There is a second way to use inline assembler by defining a whole procedure or function as assembler. | |
| 266 | 264 | ||
| 267 | 265 | ``` pascal | |
| 268 | 266 | Procedure DoSomeAssembly;assembler; | |
| 269 | 267 | asm | |
| 270 | 268 | // do some assembly | |
| 271 | 269 | end; | |
| 272 | 270 | ``` | |
| 273 | 271 | ||
| 274 | 272 | But beware, the procedure/function is called according the ABI conventions (Application binary interface). For this reasons an asm block is much easier to use than an asm procedure. For example on the target AVR according to the ABI some parameters may be passed via a register. Then the programmer has to consider this. There is no management code inserted by the compiler to access these parameters as local variable as it is done in 'normal' procedures with an asm block included. Also in opposite to the asm block the programmer has to take care of the used registers himself. Only for local variables (if defined) the compiler will insert management code for the stack frame. | |
| 275 | 273 | ||
| 276 | 274 | ## Sections | |
| 277 | 275 | ||
| 278 | 276 | ### Data | |
| 279 | 277 | ||
| 280 | 278 | Data (variables) can be placed in different linker sections by using the *section* directive. Care should be taken when placing data in *.progmem* or *.eeprom* sections, since these sections do not map to the data space for classical AVR controllers and the compiler currently doesn't generate correct code to access these sections. An example could be to place a variable in the *.noinit* section. This would place the variable in uninitialized RAM, thus a soft restart of the controller would preserve its value. | |
| 281 | 279 | ||
| 282 | 280 | Note: the *.noinit* section requires fpc 3.3.1 to work properly. | |
| 283 | 281 | ||
| 284 | 282 | ### Code | |
| 285 | 283 | ||
| 286 | 284 | Support to associate section information with procedures and functions has been implemented in the compiler (currently only available in the development version 3.3.1). The startup code has been modified (so far only for the avrsim controller) to follow more or less the [avr-libc .initN sections](https://www.nongnu.org/avr-libc/user-manual/mem_sections.html#sec_dot_init) convention: | |
| 287 | 285 | ||
| 288 | 286 | - entry point - First instruction jumps to the linker provided label __trampolines_end. After the jump instruction follows the interrupt vector table. This code is different for each controller. | |
| 289 | 287 | - Progmem data (if defined) is placed next. The __trampolines_end label points to the first address after this location. | |
| 290 | 288 | - .init0 - not used, available for user code | |
| 291 | 289 | - .init1 - not used, available for user code | |
| 292 | 290 | - .init2 - Code to initialize zero register and stack pointer | |
| 293 | 291 | - .init3 - not used, available for user code | |
| 294 | 292 | - .init4 - Code to copy initialized variables to RAM and zero memory for uninitialized variables. | |
| 295 | 293 | - .init4 - .init8 - not used, available for user code | |
| 296 | 294 | - .init9 - Jump to main | |
| 297 | 295 | ||
| 298 | 296 | Note that the finalization code generated by FPC follows directly after the rest of the code, before the .finiN sections start. Currently it isn't possible to replace FPC generated finalization code or insert code via a .fini section that would execute after the finalization code. | |
| 299 | 297 | ||
| 300 | 298 | ### Example | |
| 301 | 299 | ||
| 302 | 300 | ``` pascal | |
| 303 | 301 | program test; | |
| 304 | 302 | ||
| 305 | 303 | // Store MCU startup status early and set to 0 so that e.g. watchdog resets can be determined | |
| 306 | 304 | ||
| 307 | 305 | var | |
| 308 | 306 | startupMCUSR: byte; section '.noinit'; | |
| 309 | 307 | ||
| 310 | 308 | // This should be marked noreturn since it should fall through into the next init section | |
| 311 | 309 | procedure saveMCUSR; noreturn; section '.init1'; | |
| 312 | 310 | begin | |
| 313 | 311 | startupMCUSR := MCUSR; | |
| 314 | 312 | MCUSR := 0; | |
| 315 | 313 | end; | |
| 316 | 314 | ||
| 317 | 315 | begin | |
| 318 | 316 | if startupMCUSR = 1 then | |
| 319 | 317 | writeln('Reset cause: Power-on'); | |
| 320 | 318 | end. | |
| 321 | 319 | ``` | |
| 322 | 320 | ||
| 323 | 321 | ## Resources | |
| 324 | 322 | ||
| 325 | 323 | ### Wiki | |
| 326 | 324 | ||
| 327 | 325 | - [[Portal:Embedded|Embedded Portal]] | |
| 328 | 326 | - [[Arduino]] | |
| 329 | 327 | - [[ARM]] | |
| 330 | 328 | - [[AVR]] | |
| 331 | 329 | ||
| 332 | 330 | ### Forums | |
| 333 | 331 | ||
| 334 | 332 | - [Tutorial for building FPC-AVR and *blinky* example project](http://forum.lazarus.freepascal.org/index.php/topic,30960.0.html) | |
| 335 | 333 | - [Forum topic with AVR Project Wizard](http://forum.lazarus.freepascal.org/index.php/topic,35486.msg) | |
| 336 | 334 | ||
| 337 | 335 | --- | |
| 338 | 336 | *From the Free Pascal wiki, page "AVR Programming", as it stood on 2022-08-14 after a change by Ccrause, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
