{"id":4075,"date":"2020-06-21T10:16:37","date_gmt":"2020-06-21T15:16:37","guid":{"rendered":"https:\/\/dev.iachieved.it\/iachievedit\/?p=4075"},"modified":"2020-06-21T10:16:37","modified_gmt":"2020-06-21T15:16:37","slug":"i2c-with-the-sifive-hifive1-rev-b","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/i2c-with-the-sifive-hifive1-rev-b\/","title":{"rendered":"I2C with the SiFive HiFive1 Rev B"},"content":{"rendered":"<p>Hey kids!  Today we&#8217;re going to take a look at the <a href=\"https:\/\/www.sifive.com\/boards\/hifive1-rev-b\">SiFive HiFive1 Rev B<\/a> and <a href=\"https:\/\/github.com\/sifive\/freedom-e-sdk\">Freedom Metal<\/a> <a href=\"https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C\">I2C<\/a> API.<\/p>\n<p>I am going to be using a classic EEPROM from National Semiconductor, the <a href=\"https:\/\/datasheetspdf.com\/pdf-file\/1183283\/NationalSemiconductor\/NM24C17\/1\">NM24C17<\/a>.  The NM24C17 is a 16 <i>kilobit<\/i> (2K) EEPROM that can be written to and read from using I2C.<\/p>\n<p>If you have one of these EEPROMs lying around (and who doesn&#8217;t?) and want to use it with your HiFive1 board, you&#8217;ll also need:<\/p>\n<ul>\n<li>the datasheet<\/li>\n<li>a breadboard<\/li>\n<li>breadboard wires<\/li>\n<li>2 4.7k pull-up resistors<\/li>\n<\/ul>\n<p>What you might also want to have handy a digital logic analyzer such as the <a href=\"https:\/\/www.amazon.com\/Logic-Black-Compatible-Ultra-Portable-Frustration\/dp\/B0749G85W2\">Logic 8<\/a> from <a href=\"https:\/\/www.saleae.com\/\">Saleae<\/a>.<\/p>\n<p>The I2C circuit is a simple one, but it is important to note that the NM24C17 EEPROM does not come with I2C pull-up resistors, so we need to add them in our circuit.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/nm24c17.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/nm24c17.png\" alt=\"\" width=\"300\" height=\"240\" class=\"aligncenter size-full wp-image-4132\" \/><\/a><\/p>\n<p>Assembling everything with the HiFive1 I2C pins and providing power.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/hifive_with_eeprom.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/hifive_with_eeprom.jpg\" alt=\"\" width=\"418\" height=\"640\" class=\"aligncenter size-full wp-image-4134\" \/><\/a><\/p>\n<h2>Reading the Datasheet<\/h2>\n<p>When working with I2C devices it is <i>so<\/i> important to read through the datasheet once or twice.  Or ten times.  Datasheets can be dense and intimidating, but I have rarely come across an issue I was troubleshooting that didn&#8217;t end up being caused by not reading the datasheet closely.<\/p>\n<h2>Writing<\/h2>\n<p>Okay, let&#8217;s start coding some I2C with Freedom Metal.  We&#8217;ll start with a basic shell.<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\n#include <stdio.h>\n#include <metal\/i2c.h>\n\nint main(void) {\n\n  struct metal_i2c* i2c_device = metal_i2c_get_device(0);\n  if (!i2c_device) {\n    printf(\"Unable to obtain I2C device\\n\");\n    return -1;\n  }\n\n  metal_i2c_init(i2c_device, 100000, METAL_I2C_MASTER);\n\n  printf(\"Exit\\n\");\n  return 0;\n\n}\n<\/pre>\n<p>Working with I2C in Freedom Metal starts with including the <code>&lt;metal\/i2c.h&gt;<\/code> header file and obtaining a pointer to the I2C device with <code>metal_i2c_get_device<\/code>.  For the HiFive1 Rev B board there is only one device to get, and it&#8217;s at index 0.  Once you have a pointer to the I2C device, initialize it with <code>metal_i2c_init<\/code>.  We&#8217;ll configure our device for 100 kbits\/sec (I2C &#8220;full speed&#8221;) and as the master.<\/p>\n<p>Now, let&#8217;s look at our first write function, which will be to write a sequence of bytes to the EEPROM at a given address.  This code is very specific to the way the NM24C17 EEPROM functions.  We will be using the <code>metal_i2c_write<\/code> function which takes as its arguments:<\/p>\n<ul>\n<li>a pointer to the I2C controller device on the RISC-V chip<\/li>\n<li>the address of the I2C bus device to talk to<\/li>\n<li>the length of the message to send to the bus device<\/li>\n<li>the message to send<\/li>\n<li>a flag indicating whether or not to send the I2C stop bit<\/li>\n<\/ul>\n<p>The first argument will be our <code>struct metal_i2c* i2c_device<\/code> variable, but the address of the EEPROM on the bus is interesting.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/byte_write.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/byte_write.png\" alt=\"\" width=\"555\" height=\"204\" class=\"aligncenter size-full wp-image-4121\" \/><\/a><\/p>\n<p>At first blush it appears the address would be <code>0xa0<\/code> to account for the first four bits to transmit are <code>1 0 1 0<\/code>, and for the NM24C17 device the 3 page address bits appear to be all 0 (<i>appear<\/i> is the operative word).  That leaves us with the R\/W bit, which for a write would be 0.  <code>1010 0000b<\/code>, or <code>0xa0<\/code>, right?  <em>Wrong.<\/em>  The R\/W bit is not a part of the device address here, which leaves us with 1010000b, which is 0x50.<\/p>\n<p>The message to send to the EEPROM consists of two bytes:  the memory address in the EEPROM to write to and the value to write.  For simplicity we&#8217;ll just write the value 0xab at the address 0x00.<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nunsigned int addr   = 0x50;\nunsigned char buf[] = {0x00, 0xab};\nmetal_i2c_write(i2c_device, addr, 2, buf, METAL_I2C_STOP_ENABLE);\n<\/pre>\n<p>The final argument to <code>metal_i2c_write<\/code> is to indicate whether or not to signal an I2C stop bit upon completion.  Since the stop bit is required for us to write this data to the EEPROM we will use <code>METAL_I2C_STOP_ENABLE<\/code>.<\/p>\n<p>One thing I&#8217;ve found to be true about I2C is that if it works, it works.  If it doesn&#8217;t, you better have a digital logic analyzer on hand to look at things.<\/p>\n<h2>Reading<\/h2>\n<p>Now let&#8217;s read the data that we wrote back in.  This requires two function calls:  <code>metal_i2c_write<\/code> and <code>metal_i2c_read<\/code>.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/random_read.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/random_read.png\" alt=\"\" width=\"668\" height=\"196\" class=\"aligncenter size-full wp-image-4124\" \/><\/a><\/p>\n<p>Notice above that the first step to reading a byte from the EEPROM is to write out the address to be read from, followed by a read.  There is only one stop bit in this sequence:<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nunsigned char readbuf[1] = {0x00};\n\nif (!metal_i2c_write(i2c_device, addr, 1, readbuf, METAL_I2C_STOP_DISABLE)) {\n  if (!metal_i2c_read(i2c_device, addr, 1, readbuf, METAL_I2C_STOP_ENABLE)) {\n    printf(\"Data read = %x\\n\", readbuf[0]);\n  }\n}\n<\/pre>\n<p>Notice the use of <code>METAL_I2C_STOP_DISABLE<\/code>; this instructs the I2C controller not to signal a stop bit at the conclusion of the write.<\/p>\n<p>We make double use of the <code>readbuf<\/code> array by initializing it to the address in the EEPROM we want to read from, and then to hold the data read in.<\/p>\n<h2>Writing and Reading Multiple Bytes<\/h2>\n<p>Writing and reading one byte at a time to our EEPROM is a bit tedious, so let&#8217;s make use of the multiple-byte write.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/page_write.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2020\/06\/page_write.png\" alt=\"\" width=\"785\" height=\"188\" class=\"aligncenter size-full wp-image-4126\" \/><\/a><\/p>\n<p>In this example we send the device address (again, 0x50 for the EEPROM), followed by an address to write to in the EEPROM, and then up to 16 bytes of data.<\/p>\n<p>Before we get to writing again, I&#8217;ve made mention that 0x50 is the I2C device address of the EEPROM.  While it is, that isn&#8217;t the whole story.  That is the address for <b>page block 0<\/b> of the device, but it supports 8 page blocks.  Selecting the page block is done with the lower nibble of the device address.  For example, page block 1 can be addressed at 0x51, page block 2 at 0x52, and so on.  Each page block is 2 kilobits, or 256 bytes.<\/p>\n<p>At any rate, let&#8217;s stick with page block 0 for now and write 16 bytes:<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nunsigned int addr          = 0x50;\nunsigned char writebuf[17] = {0x00};\nsrand(time(NULL));\nfor (int i = 1; i <= 16; i++) {\n  writebuf[i] = rand() % 0xff;\n}\nmetal_i2c_write(i2c_device, addr, 17, writebuf, METAL_I2C_STOP_ENABLE);\n<\/pre>\n<p>Of course, we are actually writing 17 bytes out to the EEPROM, the first of which is the address we want to write to.<\/p>\n<p>Reading the data back in can look like this:<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nunsigned int addr = 0x50;\nunsigned char readbuf[17] = {0x00};\nmetal_i2c_write(i2c_device, addr, 1,  readbuf, METAL_I2C_STOP_DISABLE);\nmetal_i2c_read(i2c_device,  addr, 16, readbuf+1, METAL_I2C_STOP_ENABLE);\n<\/pre>\n<h2>A Smarter API<\/h2>\n<p>After understanding the basics of reading and writing to the NM24C17 it's time to write an API to encapsulate the nuts and bolts.  Our header file looks like this:<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\n#include <stdint.h>\n\ntypedef struct nm24c17_msg {\n  uint8_t pageblock; \/\/ Page block within NM24C17\n  uint8_t len;       \/\/ Length of the buffer\n  uint8_t addr;      \/\/ EEPROM address within page\n  uint8_t buf[16];   \/\/ Data\n} nm24c17_msg;\n\n\/\/ Write to the NM24C17\nint nm24c17_write(nm24c17_msg* msg);\n\n\/\/ Read from the NM24C17\nint nm24c17_read(nm24c17_msg* msg);\n<\/pre>\n<p>Our structure is arranged such that the <code>addr<\/code> byte is positioned immediately prior to the <code>buf<\/code>.  This organization allows us to take advantage of the C memory layout of the data and writing to the EEPROM.  For example:<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nint nm24c17_write(nm24c17_msg* msg) {\n  struct metal_i2c* i2c = i2c_setup();\n  if (!i2c) return -1;\n\n  unsigned int device_addr = NM24C17_BASE_ADDR | msg->page;\n  uint8_t              len = 1 + msg->len;\n\n  return metal_i2c_write(i2c, device_addr, len, &msg->addr, METAL_I2C_STOP_ENABLE);\n}\n<\/pre>\n<p>Our device address is the base address of the EEPROM (0x50) ORed with the page block number.  The length of the message to write is the length of the data buffer the user wants to write <em>plus<\/em> the address byte.  Writing starts at the address byte and continues into the buffer.<\/p>\n<p>Remember the exhortation to read the datasheet, and read it several more times?  Here is what happens if you start a write on an address not evenly divisible by 16.  Notice that our write address starts at 0x22 in page block 1.  Sixteen bytes are presumably written, but when trying to read them back in, the last two bytes read are 0xff.  Hmm.<\/p>\n<pre class=\"theme:sublime-text toolbar-overlay:false tab-convert:true tab-size:2 lang:c decode:true \">\nPage    Addr    Wrote   Read\n01      22      63      63\n01      23      7c      7c\n01      24      77      77\n01      25      7b      7b\n01      26      f2      f2\n01      27      6b      6b\n01      28      6f      6f\n01      29      c5      c5\n01      2a      30      30\n01      2b      01      01\n01      2c      67      67\n01      2d      2b      2b\n01      2e      fe      fe\n01      2f      d7      d7\n01      30      ab      ff\n01      31      76      ff\n<\/pre>\n<p>From the National Semiconductor datasheet definitions:<\/p>\n<ul>\n<li>PAGE - 16 sequential addresses (one byte each) that may be programmed during a \"Page Write\" programming cycle.<\/li>\n<li>PAGE BLOCK - 2,048 (2K) bits organized into 16 pages of addressable memory. (8 bits) x (16 bytes) x (16 pages) = 2,048 bits\"<\/li>\n<\/ul>\n<p>But wait!  The <a href=\"https:\/\/ecee.colorado.edu\/~mcclurel\/fnm24c16.pdf\">Fairchild-printed version of this EEPROM's datasheet<\/a> says a bit more:<\/p>\n<p><i>To minimize write cycle time, NM24C16\/17 offer Page Write feature, by which, up to a maximum of 16 contiguous bytes locations can be programmed all at once (instead of 16 individual byte writes). To facilitate this feature, the memory array is organized in terms of \"Pages.\" A Page consists of 16 contiguous byte locations starting at every 16-Byte address boundary (for example, starting at array address 0x00, 0x10, 0x20 etc.)<\/i><\/p>\n<p>Like I said, always read the datasheet and sometimes you have to read two of them to get the whole story.<\/p>\n<h2>Recap<\/h2>\n<p>There are four basic functions needed to use I2C with Freedom Metal:<\/p>\n<ul>\n<li>metal_i2c_get_device - obtain a pointer to the underlying I2C device on the microcontroller<\/li>\n<li>metal_i2c_init - initialize the I2C device speed and mode<\/li>\n<li>metal_i2c_write - address and send data on the bus<\/li>\n<li>metal_i2c_read - address and read data from the bus<\/li>\n<\/ul>\n<p>It really is that simple!<\/p>\n<h2>Get the Code<\/h2>\n<p>I've recently start using <a href=\"https:\/\/platformio.org\/\">PlatformIO<\/a> to develop on my HiFive1.  If you haven't checked it out I recommend you doing so; it's very easy to install and start making use of your board right away without having to manually download toolchains and JTAG interfaces.  I've posted a PlatformIO-based project on <a href=\"https:\/\/github.com\/iachievedit\/hifive1_i2c_eeprom\">GitHub<\/a> for working with the NM24C17.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey kids! Today we&#8217;re going to take a look at the SiFive HiFive1 Rev B and Freedom Metal I2C API. I am going to be using a classic EEPROM from National Semiconductor, the NM24C17. The NM24C17 is a 16 kilobit (2K) EEPROM that can be written to and read from using I2C. If you have [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4132,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22,102],"tags":[],"class_list":["post-4075","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hacking","category-hifive"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4075"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=4075"}],"version-history":[{"count":20,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4075\/revisions"}],"predecessor-version":[{"id":4140,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4075\/revisions\/4140"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media\/4132"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=4075"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=4075"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=4075"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}