{"id":2156,"date":"2015-03-03T05:52:39","date_gmt":"2015-03-03T05:52:39","guid":{"rendered":"http:\/\/41j.com\/blog\/?p=2156"},"modified":"2015-10-22T01:11:26","modified_gmt":"2015-10-22T01:11:26","slug":"mpr121-captouch-sensor-notes","status":"publish","type":"post","link":"https:\/\/41j.com\/blog\/2015\/03\/mpr121-captouch-sensor-notes\/","title":{"rendered":"MPR121 Captouch sensor notes"},"content":{"rendered":"<p>The MPR121 is a capacitive sensor that was used in the Safecast Kickstarter Geiger counter (Onyx). I figured that the code could be of use to someone else adding MPR121 support to a STM32 (libmaple) project. The code could of course be adapted to other platforms with I2C support fairly easily. The complete codebase is of course of github <a href=\"http:\/\/github.com\/Safecast\/onyxfirmware\">here<\/a><\/p>\n<p>From memory the fiddly bits were finding the right parameters to get the sensor working through the plastic enclosure (parameters hardcoded in cap_init). And working round issues were I&#8217;d randomly see all the sensors firing. There was a lot of fiddling to get the captouch to be responsive, but not to misfire&#8230;<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;mpr121.h&quot;\r\n#include &quot;i2c.h&quot;\r\n\r\n#define CAPTOUCH_ADDR 0x5A\r\n#define CAPTOUCH_I2C I2C1\r\n#define CAPTOUCH_GPIO 30\r\n\r\nGUI *system_gui;\r\nstatic struct i2c_dev *i2c;\r\nstatic uint16 touchList =  1 &lt;&lt; 8 | 1 &lt;&lt; 6 | 1 &lt;&lt; 4 | 1 &lt;&lt; 3 | 1 &lt;&lt; 2 | 1 &lt;&lt; 0;\r\n\r\nint last_key_state=0;\r\nbool captouch_disable_messages=false;\r\n\r\nvoid cap_set_disable_messages(bool b) {\r\n captouch_disable_messages=b;\r\n}\r\n\r\nstatic void mpr121Write(uint8 addr, uint8 value) {\r\n  struct i2c_msg msg;\r\n  uint8 bytes&#x5B;2];\r\n  int result;\r\n\r\n  bytes&#x5B;0] = addr;\r\n  bytes&#x5B;1] = value;\r\n\r\n  msg.addr = CAPTOUCH_ADDR;\r\n  msg.flags = 0;\r\n  msg.length = sizeof(bytes);\r\n  msg.xferred = 0;\r\n  msg.data = bytes;\r\n\r\n  result = i2c_master_xfer(i2c, &amp;msg, 1, 100);\r\n  return;\r\n}\r\n\r\nstatic uint8 mpr121Read(uint8 addr) {\r\n  struct i2c_msg msgs&#x5B;2];\r\n  uint8 byte;\r\n\r\n  byte = addr;\r\n  msgs&#x5B;0].addr   = msgs&#x5B;1].addr   = CAPTOUCH_ADDR;\r\n  msgs&#x5B;0].length = msgs&#x5B;1].length = sizeof(byte);\r\n  msgs&#x5B;0].data   = msgs&#x5B;1].data   = &amp;byte;\r\n  msgs&#x5B;0].flags = 0;\r\n  msgs&#x5B;1].flags = I2C_MSG_READ;\r\n  int result = i2c_master_xfer(i2c, msgs, 2, 100);\r\n\r\n  \/\/ retry reads\r\n  for(int n=0;(result == -1) &amp;&amp; (n&lt;5);n++) {\r\n    result = i2c_master_xfer(i2c, msgs, 2, 100);\r\n  }\r\n  if(result == -1) return 255;\r\n\r\n  return byte;\r\n}\r\n\r\nchar c&#x5B;100];\r\nchar *cap_diagdata(int e) {\r\n\r\n  int elech=0;\r\n  int elecl=0;\r\n  if(e==0)  elecl = mpr121Read(ELE0_DATAL);\r\n  if(e==0)  elech = mpr121Read(ELE0_DATAH);\r\n  if(e==1)  elecl = mpr121Read(ELE1_DATAL);\r\n  if(e==1)  elech = mpr121Read(ELE1_DATAH);\r\n  if(e==2)  elecl = mpr121Read(ELE2_DATAL);\r\n  if(e==2)  elech = mpr121Read(ELE2_DATAH);\r\n  if(e==3)  elecl = mpr121Read(ELE3_DATAL);\r\n  if(e==3)  elech = mpr121Read(ELE3_DATAH);\r\n  if(e==4)  elecl = mpr121Read(ELE4_DATAL);\r\n  if(e==4)  elech = mpr121Read(ELE4_DATAH);\r\n  if(e==5)  elecl = mpr121Read(ELE5_DATAL);\r\n  if(e==5)  elech = mpr121Read(ELE5_DATAH);\r\n  if(e==6)  elecl = mpr121Read(ELE6_DATAL);\r\n  if(e==6)  elech = mpr121Read(ELE6_DATAH);\r\n  if(e==7)  elecl = mpr121Read(ELE7_DATAL);\r\n  if(e==7)  elech = mpr121Read(ELE7_DATAH);\r\n  if(e==8)  elecl = mpr121Read(ELE8_DATAL);\r\n  if(e==8)  elech = mpr121Read(ELE8_DATAH);\r\n  if(e==9)  elecl = mpr121Read(ELE9_DATAL);\r\n  if(e==9)  elech = mpr121Read(ELE9_DATAH);\r\n  if(e==10) elecl = mpr121Read(ELE10_DATAL);\r\n  if(e==10) elech = mpr121Read(ELE10_DATAH);\r\n\r\n  elech = elech &amp; 0x3;\r\n  uint32_t elecv = ((uint32_t)elech &lt;&lt; 8) | (uint32_t)elecl;\r\n\r\n  uint32_t elec_base=0;\r\n  if(e==0)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE0_BASE)))  &lt;&lt; 2;\r\n  if(e==1)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE1_BASE)))  &lt;&lt; 2;\r\n  if(e==2)  elec_base = ((int)mpr121Read(ELE2_BASE))  &lt;&lt; 2;\r\n  if(e==3)  elec_base = ((int)mpr121Read(ELE3_BASE))  &lt;&lt; 2;\r\n  if(e==4)  elec_base = ((int)mpr121Read(ELE4_BASE))  &lt;&lt; 2;\r\n  if(e==5)  elec_base = ((int)mpr121Read(ELE5_BASE))  &lt;&lt; 2;\r\n  if(e==6)  elec_base = ((int)mpr121Read(ELE6_BASE))  &lt;&lt; 2;\r\n  if(e==7)  elec_base = ((int)mpr121Read(ELE7_BASE))  &lt;&lt; 2;\r\n  if(e==8)  elec_base = ((int)mpr121Read(ELE8_BASE))  &lt;&lt; 2;\r\n  if(e==9)  elec_base = ((int)mpr121Read(ELE9_BASE))  &lt;&lt; 2;\r\n  if(e==10) elec_base = ((int)mpr121Read(ELE10_BASE)) &lt;&lt; 2;\r\n\r\n  uint32_t bs = mpr121Read(TCH_STATL);\r\n           bs = bs | ((0x1F &amp; mpr121Read(TCH_STATH)) &lt;&lt; 8);\r\n\r\n  uint32_t is_pressed=0;\r\n  if(bs &amp; (1 &lt;&lt; e)) is_pressed=1; else is_pressed=0;\r\n\r\n  sprintf(c,&quot;%&quot;PRIu32&quot; %&quot;PRIu32&quot; %&quot;PRIu32&quot; %&quot;PRIu32&quot;&quot;,elecv,elec_base,bs,is_pressed);\r\n\r\n  return c;\r\n}\r\n\r\nbool cap_check() {\r\n  uint8 d = mpr121Read(ELE0_DATAL);\r\n  if(d == 255) return false;\r\n  return true;\r\n}\r\n\r\nuint32_t press_time&#x5B;16];\r\nuint32_t release_time&#x5B;16];\r\nuint32_t press_time_any;\r\nuint32_t release_time_any;\r\n\r\nuint32_t cap_last_press(int key) {\r\n  return press_time&#x5B;key];\r\n}\r\n\r\nuint32_t cap_last_release(int key) {\r\n  return release_time&#x5B;key];\r\n}\r\n\r\nuint32_t cap_last_press_any() {\r\n  return press_time_any;\r\n}\r\n\r\nuint32_t cap_last_release_any() {\r\n  return release_time_any;\r\n}\r\n\r\nvoid cap_clear_press() {\r\n  for(int n=0;n&lt;16;n++) press_time&#x5B;n] = 0;\r\n  for(int n=0;n&lt;16;n++) release_time&#x5B;n] = 0;\r\n  press_time_any=0;\r\n  release_time_any=0;\r\n}\r\n\r\n\/**\r\n * Interrupt handler for capacitive keyboard events\r\n *\/\r\nstatic void cap_change(void) {\r\n\r\n  \/\/ first read\r\n  int key_state=0;\r\n  key_state  = mpr121Read(TCH_STATL);\r\n  key_state |= mpr121Read(TCH_STATH) &lt;&lt; 8;\r\n\r\n\r\n  \/\/ there appears to be a bug where it suddenly outputs a lot of bits set.\r\n  unsigned int v=key_state; \/\/ count the number of bits set in v\r\n  unsigned int c; \/\/ c accumulates the total bits set in v\r\n  for (c = 0; v; c++) {\r\n    v &amp;= v - 1; \/\/ clear the least significant bit set\r\n  }\r\n  if(c &gt; 3) return;\r\n\r\n\r\n  \/\/ clear unconnected electrodes\r\n  key_state &amp;= touchList;\r\n\r\n  \/\/ detect keys pressed\r\n  int keys_pressed = key_state &amp; (~last_key_state); \/\/TODO: ! bitwise NOT\r\n\r\n  \/\/ detect keys released\r\n  int keys_released  = (~key_state) &amp; last_key_state; \/\/TODO: ! bitwise NOT\r\n\r\n   bool readok=true;\r\n\r\n  if((!captouch_disable_messages) &amp;&amp; (readok == true)) {\r\n    for (int key=0; key&lt;16; key++) {\r\n      if (keys_pressed &amp;(1&lt;&lt;key)) { system_gui-&gt;receive_key(key,KEY_PRESSED );   press_time&#x5B;key] = realtime_get_unixtime(); press_time_any=realtime_get_unixtime(); }\r\n      if (keys_released&amp;(1&lt;&lt;key)) { system_gui-&gt;receive_key(key,KEY_RELEASED); release_time&#x5B;key] = realtime_get_unixtime(); release_time_any = realtime_get_unixtime(); }\r\n    }\r\n    last_key_state = key_state;\r\n  }\r\n\r\n}\r\n\r\nbool cap_ispressed(int key) {\r\n  if (last_key_state &amp; (1&lt;&lt;key)) { return true; }\r\n                            else { return false;}\r\n}\r\n\r\nint cap_lastkey() {\r\n  return last_key_state;\r\n}\r\n\r\nuint8_t cap_mhd_r = 0x01;\r\nuint8_t cap_nhd_r = 0x01;\r\nuint8_t cap_ncl_r = 0xFF;\r\nuint8_t cap_fdl_r = 0x02;\r\n\r\nuint8_t cap_mhd_f = 0x01;\r\nuint8_t cap_nhd_f = 0x01;\r\nuint8_t cap_ncl_f = 0xFF;\r\nuint8_t cap_fdl_f = 0x02;\r\n\r\nuint8_t cap_dbr   = 0x77;\r\n\r\nuint8_t cap_touch_threshold   = 0x10;\r\nuint8_t cap_release_threshold = 0x0B;\r\n\r\nvoid cap_set_mhd_r(uint8_t v) { cap_mhd_r = v; }\r\nvoid cap_set_nhd_r(uint8_t v) { cap_nhd_r = v; }\r\nvoid cap_set_ncl_r(uint8_t v) { cap_ncl_r = v; }\r\nvoid cap_set_fdl_r(uint8_t v) { cap_fdl_r = v; }\r\n\r\nvoid cap_set_mhd_f(uint8_t v) { cap_mhd_f = v; }\r\nvoid cap_set_nhd_f(uint8_t v) { cap_nhd_f = v; }\r\nvoid cap_set_ncl_f(uint8_t v) { cap_ncl_f = v; }\r\nvoid cap_set_fdl_f(uint8_t v) { cap_fdl_f = v; }\r\n\r\nvoid cap_set_dbr  (uint8_t v) { cap_dbr = v; }\r\nvoid cap_set_touch_threshold  (uint8_t v) { cap_touch_threshold   = v; }\r\nvoid cap_set_release_threshold(uint8_t v) { cap_release_threshold = v; }\r\n\r\nvoid cap_init(void) {\r\n\r\n    \/\/ 63 2 4 1 63 2 4 1 0 8 4\r\n    cap_set_mhd_r(63);\r\n    cap_set_nhd_r(2);\r\n    cap_set_ncl_r(4);\r\n    cap_set_fdl_r(1);\r\n    cap_set_mhd_f(63);\r\n    cap_set_nhd_f(2);\r\n    cap_set_ncl_r(4);\r\n    cap_set_fdl_f(1);\r\n    cap_set_dbr(0);\r\n    cap_set_touch_threshold(8);\r\n    cap_set_release_threshold(4);\r\n\r\n    gpio_set_mode(PIN_MAP&#x5B;9].gpio_device,PIN_MAP&#x5B;9].gpio_bit,GPIO_OUTPUT_PP);\r\n    gpio_set_mode(PIN_MAP&#x5B;5].gpio_device,PIN_MAP&#x5B;5].gpio_bit,GPIO_OUTPUT_PP);\r\n    gpio_write_bit(PIN_MAP&#x5B;9].gpio_device,PIN_MAP&#x5B;9].gpio_bit,1);\r\n    gpio_write_bit(PIN_MAP&#x5B;5].gpio_device,PIN_MAP&#x5B;5].gpio_bit,1);\r\n    delay_us(1000);\r\n    gpio_set_mode(PIN_MAP&#x5B;9].gpio_device,PIN_MAP&#x5B;9].gpio_bit,GPIO_INPUT_PD); \/\/ Can also be floating, but PD is safer if components misplaced.\r\n    gpio_set_mode(PIN_MAP&#x5B;5].gpio_device,PIN_MAP&#x5B;5].gpio_bit,GPIO_INPUT_PD);\r\n\r\n    i2c = CAPTOUCH_I2C;\r\n    i2c_init(i2c);\r\n    i2c_master_enable(i2c, 0);\r\n\r\n    mpr121Write(0x80,0x63); \/\/ soft reset\r\n    delay_us(1000);\r\n    mpr121Write(ELE_CFG, 0x00);   \/\/ disable electrodes for config\r\n    delay_us(100);\r\n\r\n    \/\/ Section A and B - R (rise) F (fall) T (touch)\r\n    mpr121Write(MHD_R, cap_mhd_r); \/\/ (1 to 63)\r\n    mpr121Write(NHD_R, cap_nhd_r); \/\/ (1 to 63)\r\n    mpr121Write(NCL_R, cap_ncl_r); \/\/ (0 to 255)\r\n    mpr121Write(FDL_R, cap_fdl_r); \/\/ (0 to 255)\r\n\r\n    mpr121Write(MHD_F, cap_mhd_f); \/\/ (1 to 63) largest value to pass through filer\r\n    mpr121Write(NHD_F, cap_nhd_f); \/\/ (1 to 63) maximum change allowed\r\n    mpr121Write(NCL_F, cap_ncl_f); \/\/ (0 to 255) number of samples required to determine non-noise\r\n    mpr121Write(FDL_F, cap_fdl_f); \/\/ (0 to 255) rate of filter operation, larger = slower.\r\n\r\n    \/\/ Section D\r\n    \/\/ Set the Filter Configuration\r\n    \/\/ Set ESI2\r\n\r\n    \/\/ was 0x01, 0x25\r\n    mpr121Write(AFE_CONF, 0x01); \/\/AFE_CONF  0x5C\r\n    mpr121Write(FIL_CFG , 0x04); \/\/FIL_CFG   0x5D\r\n\r\n    \/\/ Section F\r\n    mpr121Write(ATO_CFG0, 0x0B); \/\/ ATO_CFG0 0x7B\r\n\r\n    \/\/ limits\r\n    \/\/ was0xFF,0x00,0x0E\r\n    mpr121Write(ATO_CFGU, 0x9C); \/\/ ATO_CFGU 0x7D\r\n    mpr121Write(ATO_CFGL, 0x65); \/\/ ATO_CFGL 0x7E\r\n    mpr121Write(ATO_CFGT, 0x8C); \/\/ ATO_CFGT 0x7F\r\n\r\n    \/\/ enable debouncing\r\n    mpr121Write(DBR     , cap_dbr); \/\/ set debouncing, in this case 7 for both touch and release.\r\n\r\n    \/\/ Section C\r\n    \/\/ This group sets touch and release thresholds for each electrode\r\n    mpr121Write(ELE0_T , cap_touch_threshold);\r\n    mpr121Write(ELE0_R , cap_release_threshold);\r\n    mpr121Write(ELE1_T , cap_touch_threshold);\r\n    mpr121Write(ELE1_R , cap_release_threshold);\r\n    mpr121Write(ELE2_T , cap_touch_threshold);\r\n    mpr121Write(ELE2_R , cap_release_threshold);\r\n    mpr121Write(ELE3_T , cap_touch_threshold);\r\n    mpr121Write(ELE3_R , cap_release_threshold);\r\n    mpr121Write(ELE4_T , cap_touch_threshold);\r\n    mpr121Write(ELE4_R , cap_release_threshold);\r\n    mpr121Write(ELE5_T , cap_touch_threshold);\r\n    mpr121Write(ELE5_R , cap_release_threshold);\r\n    mpr121Write(ELE6_T , cap_touch_threshold);\r\n    mpr121Write(ELE6_R , cap_release_threshold);\r\n    mpr121Write(ELE7_T , cap_touch_threshold);\r\n    mpr121Write(ELE7_R , cap_release_threshold);\r\n    mpr121Write(ELE8_T , cap_touch_threshold);\r\n    mpr121Write(ELE8_R , cap_release_threshold);\r\n    mpr121Write(ELE9_T , cap_touch_threshold);\r\n    mpr121Write(ELE9_R , cap_release_threshold);\r\n    mpr121Write(ELE10_T, cap_touch_threshold);\r\n    mpr121Write(ELE10_R, cap_release_threshold);\r\n    mpr121Write(ELE11_T, cap_touch_threshold);\r\n    mpr121Write(ELE11_R, cap_release_threshold);\r\n\r\n    delay_us(100);\r\n\r\n    \/\/ Section E\r\n    \/\/ Electrode Configuration\r\n    \/\/ Enable 6 Electrodes and set to run mode\r\n    \/\/ Set ELE_CFG to 0x00 to return to standby mode\r\n    mpr121Write(ELE_CFG, 0x0C);   \/\/ Enables all 12 Electrodes\r\n    delay_us(100);\r\n\r\n    \/\/ This can also be FLOATING, but PU is safer if components misplaced.\r\n    gpio_set_mode(PIN_MAP&#x5B;CAPTOUCH_GPIO].gpio_device,PIN_MAP&#x5B;CAPTOUCH_GPIO].gpio_bit,GPIO_INPUT_PU);\r\n    exti_attach_interrupt((afio_exti_num)(PIN_MAP&#x5B;CAPTOUCH_GPIO].gpio_bit), gpio_exti_port(PIN_MAP&#x5B;CAPTOUCH_GPIO].gpio_device), cap_change, EXTI_FALLING);\r\n\r\n    \/\/ Clears the first interrupt\r\n    for(int n=0;n&lt;16;n++) press_time&#x5B;n]   = realtime_get_unixtime();\r\n    for(int n=0;n&lt;16;n++) release_time&#x5B;n] = realtime_get_unixtime();\r\n    press_time_any   = realtime_get_unixtime();\r\n    release_time_any = realtime_get_unixtime();\r\n\r\n    return;\r\n}\r\n\r\nvoid cap_deinit(void) {\r\n  exti_detach_interrupt((afio_exti_num)(PIN_MAP&#x5B;CAPTOUCH_GPIO].gpio_bit));\r\n\r\n  \/\/ Disable MPR121 scanning, in case the chip is on\r\n  mpr121Write(ELE_CFG, 0x00);\r\n\r\n  return;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The MPR121 is a capacitive sensor that was used in the Safecast Kickstarter Geiger counter (Onyx). I figured that the code could be of use to someone else adding MPR121 support to a STM32 (libmaple) project. The code could of course be adapted to other platforms with I2C support fairly easily. The complete codebase is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"MPR121 Captouch sensor notes","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1],"tags":[],"class_list":["post-2156","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1RRoU-yM","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/2156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/comments?post=2156"}],"version-history":[{"count":2,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/2156\/revisions"}],"predecessor-version":[{"id":3024,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/2156\/revisions\/3024"}],"wp:attachment":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/media?parent=2156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/categories?post=2156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/tags?post=2156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}