{"id":19028559,"url":"https://github.com/pkarsy/rccalibrator","last_synced_at":"2025-04-23T15:43:14.576Z","repository":{"id":202091761,"uuid":"94523291","full_name":"pkarsy/rcCalibrator","owner":"pkarsy","description":"Calibration of the internal RC oscillator of atmega328p chip, and UART bootloader(ATmegaBOOT) without crystal.","archived":false,"fork":false,"pushed_at":"2024-03-28T16:09:08.000Z","size":686,"stargazers_count":20,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-18T00:57:29.469Z","etag":null,"topics":["arduino","atmega328","atmega328p","avr","bootloader","embeded","osccal","serial","uart"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pkarsy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2017-06-16T08:33:15.000Z","updated_at":"2025-02-15T16:52:27.000Z","dependencies_parsed_at":"2024-03-28T17:30:04.092Z","dependency_job_id":"b311aac6-079c-4b04-8d8f-90edf5fddf94","html_url":"https://github.com/pkarsy/rcCalibrator","commit_stats":null,"previous_names":["pkarsy/rccalibrator"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkarsy%2FrcCalibrator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkarsy%2FrcCalibrator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkarsy%2FrcCalibrator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkarsy%2FrcCalibrator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pkarsy","download_url":"https://codeload.github.com/pkarsy/rcCalibrator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250462732,"owners_count":21434654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","atmega328","atmega328p","avr","bootloader","embeded","osccal","serial","uart"],"created_at":"2024-11-08T21:11:32.897Z","updated_at":"2025-04-23T15:43:14.552Z","avatar_url":"https://github.com/pkarsy.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n![rcCalibrator](rcCalibrator.jpg)\n\n```\n First Line: Factory OSCCAL=159 Frequency=8.11Mhz Error = +1.4%\nSecond Line: Optimal OSCCAL=157 Frequency=8.02Mhz Error = +0.2%\n```\n\n# rcCalibrator\nCalibration of the internal RC oscillator of atmega328p chip, and OSCCAL aware Serial bootloader(ATmegaBOOT).\nThe hardware consists of a USBasp programmer and a DS3231 module, and of course the programmer can be\nused for its normal purpose to flash the chips. The LCD is optional.To bypass the documentation go\ndown to [installation](#software-installation)\n\n### WARNING\n- The use of the \"osccal\" utility will erase all the contents of your MCU without notice.\n- Even if you connect a crystal, the chip will ignore it ! \nUse osscal only if you are familiar with ISP programming and know how to set the chip to the old state.\n- 57600 and even more 115200 is somewhat problematic. See the section \"57600bps\"\n\n### The Problem\nA lot of newer MCU's have an internal oscillator wich is 1% factory calibrated. For those MCU's this page is irrelevant. Atmega328p (and others) however can deviate from nominal freequency up to 10% according to the manufacturer (usually 0-3% and to be fair, most of the time, very close to 0%). This can cause numerus problems, especially with Serial communications. This page is about solutions on this problem without resorting to external crystal. And the dillema crystal/RC oscillator arises only with bare atmega's. All arduino boards with atmega328 (UNO, ProMini etc) come with external crystal.\nThe AVR microcontrollers have a register called OSCCAL (Oscillator Calibration) which can\nbe used to drift the RC frequency and reduse the error to less than 1%. Unfortunatelly the register is\nvolatile and need to be set every time the MCU starts.\n\n### Purpose\nThere are a lot of internet pages to address the calibration problem, and atmel has released a lot of\nrelated papers. This project aims to offer an alternative solution :\n- To find the optimal OSCCAL value, using a USBASP programmer and a DS3231 rtc module.\n- **More importantly** to automatically build bootloader and\napplication code capable of fixing the RC frequency.\n\n### Serial communication problems\nEven with a perfect crystal, serial communication introduces\nanother type of error, as the 8MHz clock speed is not divided exactly with the standard\nserial bitrates. See [WormFood calculator](http://wormfood.net/avrbaudcalc.php) at 8Mhz\n\n```\n38400   +0.2%   This is an excellent choise when running at 8Mhz\n57600   +2.1%   Atmega328@8MHz (and proMini@8Mhz 3.3V) is capable of this serial speed\n115200  -3.5%   Atmega328@8MHz (and ProMini@8Mhz) cannot run the HW serial port at 115.2k\n```\nIf we use the internal oscillator, this error can be added or subtracted to the RC oscillator error.\n\n### Serial bootloader: Even more problems, and a solution.\nThe use of a serial/UART bootloader (a standard, not the one provided here) and at the same time using the internal RC oscillator, potentially generates a serious problem. Suppose we know that the optimal\nOSCCAL value for a specific atmega chip is 139 : We develop an Arduino application,\nand right after setup() we write:\n\n```C++\nOSCCAL=139;\n```\n\nSeems good ?\u003cbr/\u003e\n**Unfortunately it is not working.**\u003cbr/\u003e\nThe bootloader starts first, without knowing anything about the magic 139 value, and happily\nwaits for code from the UART.\nIf the chip happens to be badly factory calibrated, the serial communication will fail\nand we will not be able to upload any code to the chip.\n\nThe solution provided here is simple and very robust. \"osccal\"\nutility finds the  correct OSCCAL value, and then the (modified)ATmegaBOOT is compiled\nagainst this specific OSCCAL value. Then it is uploaded to the chip. The first\nthink the bootloader does, is to Fix the RC frequency, allowing serial communications. For another atmega chip\nthe OSCCAL value will be different, and so on.\n\n### 57600bps\n57600bps introduces +2.1% error. Suppose we have calibrated the RC oscillator and the error is\n+0.3%. Then the total error becomes +2.4% which is marginal.\u003cbr/\u003e\nI suggest if you really need to use\n57600bps in your appplication, to use an OSCCAL = OPTIMAL_OSCCAL - 4 to compensate the error.\nThe clock ( millis() and friends ) will be ~2% slower than realtime however.\u003cbr/\u003e\nThe modified ATmegaBOOT provided, does exactly this, but before jump to the application it sets\nthe OSCCAL to the optimal value (The nearest to 8Mhz), because it does not know which speed\nthe application uses.\u003cbr/\u003e\nSo the strategy is:\n\n```C++\n// Use it with the modified AtmagaBOOT\n// At this point the OSCCAL is  the optimal\n// as the bootloader runs first\nvoid setup() {\n    // The clock will be a little slow\n    // but serial communication will be perfect\n    // Note that \"osccal\" (and modified AtmagaBOOT) avoids the values 128-131\n    // and there is no danger to go from the upper OSCCAL region (128, 129, ...)\n    // to the lower (..., 126, 127)\n    // See Frequency-OSCCAL graph in the datasheet\n    OSCCAL-=4;\n    Serial.begin(57600);\n    ....\n}\n```\n\nor (recommended)\n\n```C++\nvoid setup() {\n   // No OSCCAL manipulation is needed\n   Serial.begin(38400);\n   ....\n}\n```\n\n### Reasons to use an external crystal\n- Generally whenever you need better accuracy than the RC oscillator can\nprovide. Anything more accurate than 1% should be done with external crystal/resonator\u003cbr/\u003e\n- If you need the speed (up to 20Mhz).\u003cbr/\u003e\n- When the trouble to calibrate the RC oscillator outweighs\nthe trouble to install the crystal.\u003cbr/\u003e\nI believe using the \"osccal\" utility, it is much easier (or at least, this is my intention) to have a calibrated atmega with a perfectly working bootloader, than to install the crystal. \"osccal -b\" is all that is needed.\n\n### Reasons to use the internal oscillator\n\n- Fewer parts on the breadboard/PCB. This\nis usually the first reason that comes in mind, but it is also the least importand.\nA crystal is usually a tiny part of the complexity and the cost of\na project. For simple projects is fine however, if we can avoid the crystal.\n- Ability to change the frequency at runtime. For example we can drift\nthe 8Mhz frequency -2.1% for extremely reliable 57600 serial communication and\ndrift it +3.5% for 115200. Of course we can calibrate the RC oscillator to\nthe Serial friendly 7.37(28) Mhz frequency. Note however that if you write\nArduino code, better use 8Mhz. A lot of\nuseful Arduino functions like millis() work correctly only for 8Mhz and 16Mhz\n- You have 2 additional GPIO pins. The XTAL1 and XTAL2 can be used for any purpose.\nA lot of projects need a lot of GPIO pins, and 2 more pins\ncan make the difference. I include a very simple \"library\" to control these pins\n[xtal.h](xtal.h)\n- A lot of projects don't need any accuracy of RC oscillator.\n- **Much faster startup from sleep mode.** This is important for low power projects.\nSpecifically I have a project where the MCU is in sleep, it is connected to a GSM modem with\nhardware serial(UART), and wakes up from an incoming SMS (or a TCP packet).\nHere is the received message, when the (calibrated) RC oscillator is in use.\u003cbr/\u003e\n**\"+CMT: \"+30691234567\",\"pkar\",\"17/06/18,08:10:41+12\"**\u003cbr/\u003e\nHere is the message, if we use a crystal\u003cbr/\u003e\n**S��������\u0012b\u0012���ɉ,\"17/06/18,09:51:13+12\"**\u003cbr/\u003e\nThe crystal needs a lot of time to stabilize it's frequency. As you can\nsee the incoming phone number is lost. Sometimes even the date and time. Ok there are other solutions, for example flow control, but are complex and also need extra hardware setup.\n- This one seems a little strange, but is totally valid. The internal oscillator\nhas a lot of [jitter](https://en.wikipedia.org/wiki/Jitter), not usually a good thing, but can be used as an excellent source of randomness. In conjunction with the Watchdog\ntimer (which has its own RC oscillator), can be used to generate ***true*** random numbers much faster than the\nCrystal-Watchdog combination.\n\n### How \"osccal\" utility works\nWhen \"osscal\" utility runs, it installs the \"calibrator.hex\" file to the MCU. This Arduino sketch calculates the optimal OSCCAL, using the DS3231 RTC module as clock reference. If the LCD is installed, it displays the values to the tiny screen. Finally and most\nimportantly it saves the OSCCAL value as:\n\n**EEPROM byte 0: 0x05\u003cbr/\u003e\nEEPROM byte 1: OSCCAL\u003cbr/\u003e\nEEPROM byte 2: 255-OSCCAL\u003cbr/\u003e\nEEPROM byte 3: 0x05**\n\nAfter a few seconds \"osccal\" reads back the value from the EEPROM, and prints it\nto the console.\n\n\n### Assembling the hardware\nAlthough the photo at the start of the page says it all, here are some instructions:\n- You need a usbasp ISP programmer. I recommend to use a module with 3.3V/5V option and switch it to the voltage you are going to run the atmega328 after the calibration. Probably the voltage will be 3.3V as we talk about a 8Mhz system.\n- a ZIF developer board and\n- a DS3231 RTC module.\n- a few female-female Dupont 2.54 cables.\n- If you want visual feedback, you need also a 16x2 LCD and an LCD i2c adapter(search ebay), and to solder the secondary\ni2c header of the DS3231 module. The LCD modules\ncome as 5V and 3.3V variants. Probably you need the 3.3V as mentioned above.\n\nDeveloper Board | Rtc | LCD (if you use it) | Cable color\n---|---|---|---\n+/VCC  | VCC | VCC | Red\n-/GND  | GND | GND | Black\nPC4(Arduino A4)  |   SDA | SDA | Green\nPC5(Arduino A5)  |   SCL | SCL | Yellow\nPC3(Arduino A3)  |   SQW |   - | Gray\n\n\n### Software installation\nThe following instructions are for the linux command line (tested with Ubuntu 22.04,linux mint 21.2).\n\n```sh\n# The development environment, and git of course\n\u003e sudo apt-get install avr-libc avrdude git\n...\n\u003e cd ~/Projects  # Change with the directory you will put the rcCalibrator\n\u003e git clone https://github.com/pkarsy/rcCalibrator.git\n\u003e cd rcCalibrator\n# \"osccal\" is a python script\n\u003e chmod +x osccal\n```\n\nNote: There is no much point trying to use the avrdude wich comes with Arduino tarbals, as the\nstandard (debian/ubuntu) avrdude works fine.\n\nOptionally put the \"osccal\" executable to the PATH. In most desktop oriented distributions\na symlink is enough:\n```sh\n# if the ~/bin does not exist \"mkdir ~/bin\" and then logout and login again\n\u003e cd ~/bin\n# Do NOT copy osccal, just symlink it\n\u003e ln -s ~/Projects/rcCalibrator/osccal\n```\n\n### Usage\n\nThere are 2 strategies:\n- To find some \"good\" atmegas and use them on serial applications. This of\ncourse works only if you have a lot of atmegas and only some of them\nneed to be calibrated. This method has the advantage that no\nmodification of existing code is needed. If you need 57600 speed this\nmethod is unreliable however. See \"Serial communication problems\" above. In fact\nchips with about -1.5% to -2.5% error (Not 0% !) work the best for 57600bps.\n- To be used with a custom bootloader who sets the OSCCAL register at\nstartup. I have modified the ATmegaBOOT (used in\nArduino proMini) to do exactly this. It sets the speed at about -2% (Reduces OSCCAL register by 4) of\nthe optimal 8Mhz value, to make 57600 upload very reliable because it conpensates the +2.1% error\n(See \"Serial communication problems\" above) , and just\nbefore the application code starts, sets the OSCCAL to the optimal\nvalue for 8Mhz frequency, Although it can easily support 115200bps\nby drifting the speed to +3.5% of the optimal, I\nwanted it to be interchangeable with ProMini 3.3V@8Mhz  so you don't need to\ndefine/use a custom board in the Arduino build system. \"Standard is better than\nbetter\"\n\n### Running the \"osccal\" utility .\nConnect the usbasp programmer (with the RTC) to a USB port,\nattach a atmega chip, and run the executable :\n```sh\n# use the full path name ie \"~/Projects/rcCalibrator/osccal\" if\n# the executable is not in the PATH\n\u003e osccal\n```\nAfter a few seconds you will see the optimal osccal value in the command\nline. If you have the LCD, you will also see the results there.\nSo from the perspective of the computer, \"osccal\" is a command witch\ngives a number as a result (The best OSCCAL value = the closer to 8Mhz).\n\n### Modified AtmegaBOOT installation\nThis can be used with a bare atmega328 **without a crystal**. Dont do this on arduino boards.\n\n```sh\n\u003e osccal -b\n```\n\nwait a few seconds ... ready !\nThe chip can now programmed as a proMini 3.3V @ 8MHz.\n**Note however that even if you install a crystal, the chip will use the internal RC oscilator until you fix the fuses**\n\nIt is also important to note that the bootloader does NOT use any predefined EEPROM or FLASH location to read the OSCCAL. This avoids the danger to accidentally erase the EEPROM by the application (to store some data), with probably catastrophic results for the project. The bootloader is recompiled for every new chip and the OSCCAL value is saved in the bootloader area and is unique for this chip. This is the reason there is no precompiled HEX for this bootloader.\n\n### Comparing ATmegaBoot(with OSCCAL support) with the stock ATmegaBOOT/optiboot\nThere are some pages around, that give instructions to use\nan uncalibrated atmega328p with a 38400 bootloader(usually the stock optiboot or ATmegaBOOT). This is unreliable however, as some chips come from the factory with clock errors far worse than 2%. It is also non standard and requires an Arduino custom board definition (as far as I know).\u003cbr/\u003e\nThe ATmegaBOOT Makefile included here, uses the \"osccal\" utility to find the correct OSCCAL value. It compiles the ATmegaBOOT against this value and then uploads the .hex file to the atmega328p chip. This chip can then be used just like a proMini to upload code with 57600bps. Indeed, according to my tests, the upload process is as reliable as with a crystal.\n\n### applications without bootloader\nYou have to modify the Makefile of your project to use the \"osscal\" as a shell command and\nget the OSCCAL value before write the application code. Somewhere inside the application\njust after main() or setup() and before Serial initialization, put a\n\n```C++\nOSCCAL = OPTIMAL_OSCCAL_VALUE;\nSerial.begin(38400);\n```\n\nor if you insist on using 57600bps\n\n```C++\nOSCCAL = OPTIMAL_OSCCAL_VALUE-4; // The MCU runs ~2% slower\nSerial.begin(57600);\n```\n\nOPTIMAL_OSCCAL_VALUE must be passed to the gcc by the Makefile.\nSee the Makefile of the ATmegaBOOT bootloader, included here.\n\n### Alternative method. Read the OSCCAL value from EEPROM\nSee [How \"osccal\" utility works](#how-osccal-utility-works) above.\u003cbr/\u003e\nThe application reads the EEPROM and uses the OSCCAL value provided.\nHowever I find the method quite fragile. A programming mistake can overwrite the contents of the EEPROM.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkarsy%2Frccalibrator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpkarsy%2Frccalibrator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkarsy%2Frccalibrator/lists"}