Archive for the ‘Uncategorized’ Category.

Chuo Seiki MMU-60X-H1 XY Stage

chuoseiki

I picked up this Chuo Seiki XY stage a while ago. It has 25mm travel and 2 micron resolution! It also uses Vexta 5-phase steppers (I believe PH533-NB-C30s). However it didn’t come with any drivers. I’ve never driven a 5-phase stepper before but I’ve ordered the Oriental Motor drivers, and will probably try and make my own at some point. It uses Hirose HR10 series connectors (HR10A-10P-12S(73)s). Which I’ll also need to buy.

It should be an interesting project in any case, and I hope to use it to automate my optical microscope stage.

I’ve also got another Chuo Seiki XYZ stage on it’s which reportedly has 1 micron resolution, hopefully I’ll be able to report back on both of them when I get to the UK.

Simple scatter plot in gnuplot

I can never recall the code to do a scatter plot in gnuplot. So here it is for reference:

1
2
3
4
5
6
7
8
set size square
 
set ylabel "YLabel"
set xlabel "XLabel"
 
set terminal postscript eps color
set output "plot.eps"
plot "data" using 1:2 with points

This creates an eps file called plot.eps from a file called data. The file should contain 2 columns, the first being the x axis and the second the y.

MPR121 Captouch sensor notes

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 here

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’d randomly see all the sensors firing. There was a lot of fiddling to get the captouch to be responsive, but not to misfire…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#include "mpr121.h"
#include "i2c.h"
 
#define CAPTOUCH_ADDR 0x5A
#define CAPTOUCH_I2C I2C1
#define CAPTOUCH_GPIO 30
 
GUI *system_gui;
static struct i2c_dev *i2c;
static uint16 touchList =  1 << 8 | 1 << 6 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 0;
 
int last_key_state=0;
bool captouch_disable_messages=false;
 
void cap_set_disable_messages(bool b) {
 captouch_disable_messages=b;
}
 
static void mpr121Write(uint8 addr, uint8 value) {
  struct i2c_msg msg;
  uint8 bytes[2];
  int result;
 
  bytes[0] = addr;
  bytes[1] = value;
 
  msg.addr = CAPTOUCH_ADDR;
  msg.flags = 0;
  msg.length = sizeof(bytes);
  msg.xferred = 0;
  msg.data = bytes;
 
  result = i2c_master_xfer(i2c, &msg, 1, 100);
  return;
}
 
static uint8 mpr121Read(uint8 addr) {
  struct i2c_msg msgs[2];
  uint8 byte;
 
  byte = addr;
  msgs[0].addr   = msgs[1].addr   = CAPTOUCH_ADDR;
  msgs[0].length = msgs[1].length = sizeof(byte);
  msgs[0].data   = msgs[1].data   = &byte;
  msgs[0].flags = 0;
  msgs[1].flags = I2C_MSG_READ;
  int result = i2c_master_xfer(i2c, msgs, 2, 100);
 
  // retry reads
  for(int n=0;(result == -1) && (n<5);n++) {
    result = i2c_master_xfer(i2c, msgs, 2, 100);
  }
  if(result == -1) return 255;
 
  return byte;
}
 
char c[100];
char *cap_diagdata(int e) {
 
  int elech=0;
  int elecl=0;
  if(e==0)  elecl = mpr121Read(ELE0_DATAL);
  if(e==0)  elech = mpr121Read(ELE0_DATAH);
  if(e==1)  elecl = mpr121Read(ELE1_DATAL);
  if(e==1)  elech = mpr121Read(ELE1_DATAH);
  if(e==2)  elecl = mpr121Read(ELE2_DATAL);
  if(e==2)  elech = mpr121Read(ELE2_DATAH);
  if(e==3)  elecl = mpr121Read(ELE3_DATAL);
  if(e==3)  elech = mpr121Read(ELE3_DATAH);
  if(e==4)  elecl = mpr121Read(ELE4_DATAL);
  if(e==4)  elech = mpr121Read(ELE4_DATAH);
  if(e==5)  elecl = mpr121Read(ELE5_DATAL);
  if(e==5)  elech = mpr121Read(ELE5_DATAH);
  if(e==6)  elecl = mpr121Read(ELE6_DATAL);
  if(e==6)  elech = mpr121Read(ELE6_DATAH);
  if(e==7)  elecl = mpr121Read(ELE7_DATAL);
  if(e==7)  elech = mpr121Read(ELE7_DATAH);
  if(e==8)  elecl = mpr121Read(ELE8_DATAL);
  if(e==8)  elech = mpr121Read(ELE8_DATAH);
  if(e==9)  elecl = mpr121Read(ELE9_DATAL);
  if(e==9)  elech = mpr121Read(ELE9_DATAH);
  if(e==10) elecl = mpr121Read(ELE10_DATAL);
  if(e==10) elech = mpr121Read(ELE10_DATAH);
 
  elech = elech & 0x3;
  uint32_t elecv = ((uint32_t)elech << 8) | (uint32_t)elecl;
 
  uint32_t elec_base=0;
  if(e==0)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE0_BASE)))  << 2;
  if(e==1)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE1_BASE)))  << 2;
  if(e==2)  elec_base = ((int)mpr121Read(ELE2_BASE))  << 2;
  if(e==3)  elec_base = ((int)mpr121Read(ELE3_BASE))  << 2;
  if(e==4)  elec_base = ((int)mpr121Read(ELE4_BASE))  << 2;
  if(e==5)  elec_base = ((int)mpr121Read(ELE5_BASE))  << 2;
  if(e==6)  elec_base = ((int)mpr121Read(ELE6_BASE))  << 2;
  if(e==7)  elec_base = ((int)mpr121Read(ELE7_BASE))  << 2;
  if(e==8)  elec_base = ((int)mpr121Read(ELE8_BASE))  << 2;
  if(e==9)  elec_base = ((int)mpr121Read(ELE9_BASE))  << 2;
  if(e==10) elec_base = ((int)mpr121Read(ELE10_BASE)) << 2;
 
  uint32_t bs = mpr121Read(TCH_STATL);
           bs = bs | ((0x1F & mpr121Read(TCH_STATH)) << 8);
 
  uint32_t is_pressed=0;
  if(bs & (1 << e)) is_pressed=1; else is_pressed=0;
 
  sprintf(c,"%"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"",elecv,elec_base,bs,is_pressed);
 
  return c;
}
 
bool cap_check() {
  uint8 d = mpr121Read(ELE0_DATAL);
  if(d == 255) return false;
  return true;
}
 
uint32_t press_time[16];
uint32_t release_time[16];
uint32_t press_time_any;
uint32_t release_time_any;
 
uint32_t cap_last_press(int key) {
  return press_time[key];
}
 
uint32_t cap_last_release(int key) {
  return release_time[key];
}
 
uint32_t cap_last_press_any() {
  return press_time_any;
}
 
uint32_t cap_last_release_any() {
  return release_time_any;
}
 
void cap_clear_press() {
  for(int n=0;n<16;n++) press_time[n] = 0;
  for(int n=0;n<16;n++) release_time[n] = 0;
  press_time_any=0;
  release_time_any=0;
}
 
/**
 * Interrupt handler for capacitive keyboard events
 */
static void cap_change(void) {
 
  // first read
  int key_state=0;
  key_state  = mpr121Read(TCH_STATL);
  key_state |= mpr121Read(TCH_STATH) << 8;
 
 
  // there appears to be a bug where it suddenly outputs a lot of bits set.
  unsigned int v=key_state; // count the number of bits set in v
  unsigned int c; // c accumulates the total bits set in v
  for (c = 0; v; c++) {
    v &= v - 1; // clear the least significant bit set
  }
  if(c > 3) return;
 
 
  // clear unconnected electrodes
  key_state &= touchList;
 
  // detect keys pressed
  int keys_pressed = key_state & (~last_key_state); //TODO: ! bitwise NOT
 
  // detect keys released
  int keys_released  = (~key_state) & last_key_state; //TODO: ! bitwise NOT
 
   bool readok=true;
 
  if((!captouch_disable_messages) && (readok == true)) {
    for (int key=0; key<16; key++) {
      if (keys_pressed &(1<<key)) { system_gui->receive_key(key,KEY_PRESSED );   press_time[key] = realtime_get_unixtime(); press_time_any=realtime_get_unixtime(); }
      if (keys_released&(1<<key)) { system_gui->receive_key(key,KEY_RELEASED); release_time[key] = realtime_get_unixtime(); release_time_any = realtime_get_unixtime(); }
    }
    last_key_state = key_state;
  }
 
}
 
bool cap_ispressed(int key) {
  if (last_key_state & (1<<key)) { return true; }
                            else { return false;}
}
 
int cap_lastkey() {
  return last_key_state;
}
 
uint8_t cap_mhd_r = 0x01;
uint8_t cap_nhd_r = 0x01;
uint8_t cap_ncl_r = 0xFF;
uint8_t cap_fdl_r = 0x02;
 
uint8_t cap_mhd_f = 0x01;
uint8_t cap_nhd_f = 0x01;
uint8_t cap_ncl_f = 0xFF;
uint8_t cap_fdl_f = 0x02;
 
uint8_t cap_dbr   = 0x77;
 
uint8_t cap_touch_threshold   = 0x10;
uint8_t cap_release_threshold = 0x0B;
 
void cap_set_mhd_r(uint8_t v) { cap_mhd_r = v; }
void cap_set_nhd_r(uint8_t v) { cap_nhd_r = v; }
void cap_set_ncl_r(uint8_t v) { cap_ncl_r = v; }
void cap_set_fdl_r(uint8_t v) { cap_fdl_r = v; }
 
void cap_set_mhd_f(uint8_t v) { cap_mhd_f = v; }
void cap_set_nhd_f(uint8_t v) { cap_nhd_f = v; }
void cap_set_ncl_f(uint8_t v) { cap_ncl_f = v; }
void cap_set_fdl_f(uint8_t v) { cap_fdl_f = v; }
 
void cap_set_dbr  (uint8_t v) { cap_dbr = v; }
void cap_set_touch_threshold  (uint8_t v) { cap_touch_threshold   = v; }
void cap_set_release_threshold(uint8_t v) { cap_release_threshold = v; }
 
void cap_init(void) {
 
    // 63 2 4 1 63 2 4 1 0 8 4
    cap_set_mhd_r(63);
    cap_set_nhd_r(2);
    cap_set_ncl_r(4);
    cap_set_fdl_r(1);
    cap_set_mhd_f(63);
    cap_set_nhd_f(2);
    cap_set_ncl_r(4);
    cap_set_fdl_f(1);
    cap_set_dbr(0);
    cap_set_touch_threshold(8);
    cap_set_release_threshold(4);
 
    gpio_set_mode(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,GPIO_OUTPUT_PP);
    gpio_set_mode(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,GPIO_OUTPUT_PP);
    gpio_write_bit(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,1);
    gpio_write_bit(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,1);
    delay_us(1000);
    gpio_set_mode(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,GPIO_INPUT_PD); // Can also be floating, but PD is safer if components misplaced.
    gpio_set_mode(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,GPIO_INPUT_PD);
 
    i2c = CAPTOUCH_I2C;
    i2c_init(i2c);
    i2c_master_enable(i2c, 0);
 
    mpr121Write(0x80,0x63); // soft reset
    delay_us(1000);
    mpr121Write(ELE_CFG, 0x00);   // disable electrodes for config
    delay_us(100);
 
    // Section A and B - R (rise) F (fall) T (touch)
    mpr121Write(MHD_R, cap_mhd_r); // (1 to 63)
    mpr121Write(NHD_R, cap_nhd_r); // (1 to 63)
    mpr121Write(NCL_R, cap_ncl_r); // (0 to 255)
    mpr121Write(FDL_R, cap_fdl_r); // (0 to 255)
 
    mpr121Write(MHD_F, cap_mhd_f); // (1 to 63) largest value to pass through filer
    mpr121Write(NHD_F, cap_nhd_f); // (1 to 63) maximum change allowed
    mpr121Write(NCL_F, cap_ncl_f); // (0 to 255) number of samples required to determine non-noise
    mpr121Write(FDL_F, cap_fdl_f); // (0 to 255) rate of filter operation, larger = slower.
 
    // Section D
    // Set the Filter Configuration
    // Set ESI2
 
    // was 0x01, 0x25
    mpr121Write(AFE_CONF, 0x01); //AFE_CONF  0x5C
    mpr121Write(FIL_CFG , 0x04); //FIL_CFG   0x5D
 
    // Section F
    mpr121Write(ATO_CFG0, 0x0B); // ATO_CFG0 0x7B
 
    // limits
    // was0xFF,0x00,0x0E
    mpr121Write(ATO_CFGU, 0x9C); // ATO_CFGU 0x7D
    mpr121Write(ATO_CFGL, 0x65); // ATO_CFGL 0x7E
    mpr121Write(ATO_CFGT, 0x8C); // ATO_CFGT 0x7F
 
    // enable debouncing
    mpr121Write(DBR     , cap_dbr); // set debouncing, in this case 7 for both touch and release.
 
    // Section C
    // This group sets touch and release thresholds for each electrode
    mpr121Write(ELE0_T , cap_touch_threshold);
    mpr121Write(ELE0_R , cap_release_threshold);
    mpr121Write(ELE1_T , cap_touch_threshold);
    mpr121Write(ELE1_R , cap_release_threshold);
    mpr121Write(ELE2_T , cap_touch_threshold);
    mpr121Write(ELE2_R , cap_release_threshold);
    mpr121Write(ELE3_T , cap_touch_threshold);
    mpr121Write(ELE3_R , cap_release_threshold);
    mpr121Write(ELE4_T , cap_touch_threshold);
    mpr121Write(ELE4_R , cap_release_threshold);
    mpr121Write(ELE5_T , cap_touch_threshold);
    mpr121Write(ELE5_R , cap_release_threshold);
    mpr121Write(ELE6_T , cap_touch_threshold);
    mpr121Write(ELE6_R , cap_release_threshold);
    mpr121Write(ELE7_T , cap_touch_threshold);
    mpr121Write(ELE7_R , cap_release_threshold);
    mpr121Write(ELE8_T , cap_touch_threshold);
    mpr121Write(ELE8_R , cap_release_threshold);
    mpr121Write(ELE9_T , cap_touch_threshold);
    mpr121Write(ELE9_R , cap_release_threshold);
    mpr121Write(ELE10_T, cap_touch_threshold);
    mpr121Write(ELE10_R, cap_release_threshold);
    mpr121Write(ELE11_T, cap_touch_threshold);
    mpr121Write(ELE11_R, cap_release_threshold);
 
    delay_us(100);
 
    // Section E
    // Electrode Configuration
    // Enable 6 Electrodes and set to run mode
    // Set ELE_CFG to 0x00 to return to standby mode
    mpr121Write(ELE_CFG, 0x0C);   // Enables all 12 Electrodes
    delay_us(100);
 
    // This can also be FLOATING, but PU is safer if components misplaced.
    gpio_set_mode(PIN_MAP[CAPTOUCH_GPIO].gpio_device,PIN_MAP[CAPTOUCH_GPIO].gpio_bit,GPIO_INPUT_PU);
    exti_attach_interrupt((afio_exti_num)(PIN_MAP[CAPTOUCH_GPIO].gpio_bit), gpio_exti_port(PIN_MAP[CAPTOUCH_GPIO].gpio_device), cap_change, EXTI_FALLING);
 
    // Clears the first interrupt
    for(int n=0;n<16;n++) press_time[n]   = realtime_get_unixtime();
    for(int n=0;n<16;n++) release_time[n] = realtime_get_unixtime();
    press_time_any   = realtime_get_unixtime();
    release_time_any = realtime_get_unixtime();
 
    return;
}
 
void cap_deinit(void) {
  exti_detach_interrupt((afio_exti_num)(PIN_MAP[CAPTOUCH_GPIO].gpio_bit));
 
  // Disable MPR121 scanning, in case the chip is on
  mpr121Write(ELE_CFG, 0x00);
 
  return;
}

MMA7455L 3-axis accelerometer notes

The Safecast X Kickstarter Geiger counter which I wrote the firmware for contained a MMA7455L 3-axis accelerometer. The MMA7455L is an I2C device and it’s fairly easy to work with.

We didn’t end up doing much with the accelerometer but the readings were stored in the log files. I figured it might be interesting to filter the readings based on device orientation at some point.

Here’s the relevant portion of the code, it was written for the STM32 (and libmaple) but should be easily adaptable for other platforms, accel_read_state wakeups up the accelerometer and grabs a reading, before sending it back to sleep, this fit the use-case in the geiger counter where we were only taking periodic readings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "i2c.h"
#include <stdint.h>
#define ACCEL_I2C I2C1
#define ACCEL_ADDR 0x1D
 
static struct i2c_dev *i2c = ACCEL_I2C;
 
static int accel_write(uint8 addr, uint8 value) {
    struct i2c_msg msg;
    uint8 bytes[2];
    int result;
 
    bytes[0] = addr;
    bytes[1] = value;
 
    msg.addr    = ACCEL_ADDR;
    msg.flags   = 0;
    msg.length  = sizeof(bytes);
    msg.xferred = 0;
    msg.data    = bytes;
 
    result = i2c_master_xfer(i2c, &msg, 1, 1);
 
    return result;
}
 
static uint8 accel_read(uint8 addr) {
    struct i2c_msg msgs[2];
    uint8 byte;
 
    byte = addr;
    msgs[0].addr   = msgs[1].addr   = ACCEL_ADDR;
    msgs[0].length = msgs[1].length = sizeof(byte);
    msgs[0].data   = msgs[1].data   = &byte;
    msgs[0].flags = 0;
    msgs[1].flags = I2C_MSG_READ;
    i2c_master_xfer(i2c, msgs, 2, 1);
    return byte;
}
 
 
static void accel_wakeup(void) {
    /* Set the mode to "measurement", measuring 2g */
    accel_write(0x16, 0x04 | 0x01);
}
 
static int accel_ready(void) {
    return (accel_read(0x09) & 1);
}
 
static int accel_sleep(void) {
    return accel_write(0x16, 0);
}
 
 
uint8 accel_read_state(int16 *x, int16 *y, int16 *z) {
  struct i2c_msg msgs[2];
  signed char values[6];
  uint8 addr = 0x00; /* 10-bits read value */
  int32 result = 0;
 
  accel_wakeup();
  for(int n=0;(!accel_ready()) && (n < 10);n++) delay_us(1000);
  if(!accel_ready()) return 200;
 
  msgs[0].addr   = ACCEL_ADDR;
  msgs[0].length = sizeof(uint8_t);
  msgs[0].data   = &addr;
  msgs[0].flags  = 0;
 
  msgs[1].addr   = ACCEL_ADDR;
  msgs[1].length = sizeof(values);
  msgs[1].data   = (uint8 *)values;
  msgs[1].flags  = I2C_MSG_READ;
 
  result = i2c_master_xfer(i2c, msgs, 2, 1);
 
  if(result == I2C_STATE_ERROR  ) return 100;
  if(result == I2C_ERROR_TIMEOUT) return 10;
 
  if (x)
      *x = (values[1]<<2) | (values[0]);
  if (y)
      *y = (values[3]<<2) | (values[2]);
  if (z)
      *z = (values[5]<<2) | (values[4]);
 
  accel_sleep();
 
  return 0;
}
 
int accel_init(void) {
  return 0;
}
 
int accel_deinit() {
    // suspends accel
    accel_write(0x16, 0);
    return 0;
}