8-bit A/D Converter 原理
今天要說明的是 馬達制動 (Mortor Brake) ; Mortor Brake, brake 這個字不是我拼錯了, 它中文就是 "制動" 的意思. 在此我必須先說明 8-bit A/D Converter. 為什麼馬達制動會與 AD Converter 有關呢? 原因是馬達 正轉/反轉 轉到我們所希望控制的位置停下來, 這停下來的位置必須有一個參考, 否則馬達怎麼知道轉向以及何時停止轉動? 這個參考在本文中使用 類比輸入 (Analog Input) 來做參考. 馬達在不同位置時 Analog Input 會不同, 這裡我們假設示 0.372~2.77 V (伏特) .
如本文最上方的示意圖所示: 8-bit AD Converter 原理是 Digital OutPut (圖示 Digital OutPut 4條線, 8-bit 應為 8條線) 至 D/A 線路, 這 D/A 線路可能會需要 OP放大 (運算放大器) , 再與 類比輸入 (Analog Input) 來比較 (COMPARATOR) . 8-bit 定址至 255, 著 Digital OutPut 由 0 往 255 遞增, D/A 輸出之 Analog Output 也會遞增. 起出 Analog Input 與 Analog Output 比較 LOGIC 為 0, 遞增至臨界值, 這比較器 (COMPARATOR) 回突然 LOGIC 為 1; LOGIC 由 0 變為 1 的時機, Digital OutPut 所輸出的 binary 數值, 就是 8-bit A/D 值.
PCA9555 I2C to GPIO (IO Extender)
這裡我所示範的 IO 是使用 PCA9555 IO Extender, 下方圖示是它的方塊圖:
PCA9555 Host 介面是 I2C BUS, 多個 I2C DEVICE 可以並接, 但每一 DEVICE 的 SLAVE ADDRESS (BUS ADDRESS) 必須不同;
因此 PCA_9555_SLAVE_1 / PCA_9555_SLAVE_2 我們設為 0x20 與 0x21 .
#define PCA_9555_SLAVE_1 0X20 #define PCA_9555_SLAVE_2 0X21
上圖 A2, A1, A0 即是 I2C SLAVE ADDRESS 最低 3個位元. PCA_9555_SLAVE_1=0x20 最低 3個位元是 b000; 也就是 A2=0, A1=0, A0=0 ; PCA_9555_SLAVE_2=0x21 最低 3個位元是 b001; 也就是 A2=0, A1=0, A0=1 .
#define PCA_9555_SLAVE_1 0X20 #define PCA_9555_SLAVE_2 0X21
PCA9555 Registers 是固定的: 詳見 datasheet .
#define PCA_9555_INPUT_0 0 #define PCA_9555_INPUT_1 1 #define PCA_9555_OUTPUT_0 2 #define PCA_9555_OUTPUT_1 3 #define PCA_9555_POLARITY_0 4 #define PCA_9555_POLARITY_1 5 #define PCA_9555_CONFIG_0 6 #define PCA_9555_CONFIG_1 7
I2C_WRITE_DATA / I2C_READ_DATA
這裡我們需要 I2C READ / WRITE 函式.
int i2c_write_data(int fd_i2c, int bus_adr, int sub_adr, const unsigned char *data, int length) {
int res;
char buf[256];
int i;
if (0 == data || 0 >= fd_i2c) return -1;
res = ioctl(fd_i2c, I2C_SLAVE_FORCE, bus_adr);
if(res < 0) return -1;
buf[0] = sub_adr;
for(i = 1; i <= length; i++) buf[i] = data[i -1];
if(write(fd_i2c, buf, length + 1) != (length + 1)) return -1;
return length;
}
int i2c_read_data(int fd_i2c, int bus_adr, int sub_adr, unsigned char *data, int length) {
int res;
if (0 == data || 0 >= fd_i2c) return -1;
res = ioctl(fd_i2c, I2C_SLAVE_FORCE, bus_adr);
if(res < 0) return -1;
if(write(fd_i2c, &sub_adr, 1) != 1) return -1;
return read(fd_i2c, data, length);
}
8-bit A/D Converter - ReadI2cAdc()
來看一下 ReadI2cAdc() 這個函式:
// 返回 0:失敗 1:成功 ; 參數傳回 value
static int ReadI2cAdc(int fd, unsigned char *value) {
int i;
int probe = 0;
unsigned char X = 0;
unsigned char Y = 0;
static unsigned char O = 0; // last X
if (-1 == fd) return 0;
for (i = 0; i < 256; i++) {
if (-1 == i2c_write_data(fd, PCA_9555_SLAVE_2, PCA_9555_OUTPUT_1, &X, 1)) {
printf("(%s %d) i2c_write_data FAIL \n", __FILE__, __LINE__);
return 0;
}
usleep(1000);
if (-1 == i2c_read_data(fd, PCA_9555_SLAVE_2, PCA_9555_INPUT_0, &Y, 1)) {
printf("(%s %d) i2c_read_data FAIL \n", __FILE__, __LINE__);
return 0;
}
usleep(1000);
// COMPARATOR
if ((Y & BIT7) == BIT7) {
probe = 1;
break;
}
O = X;
if (X >= 255) X = 0;
else X++;
}
if (0 == probe) return 0;
*value = O; // 傳回上一次的 DIGITAL OUTPUT
return 1;
}
Digital OutPut 接線至 PCA_9555_SLAVE_2 之 PORT 1 :
i2c_write_data(fd, PCA_9555_SLAVE_2, PCA_9555_OUTPUT_1, &X, 1);
COMPARATOR LOGIC 接線至 PCA_9555_SLAVE_2 PORT-0 裡面的 IO7 也就是 P0_7
i2c_read_data(fd, PCA_9555_SLAVE_2, PCA_9555_INPUT_0, &Y, 1); if ((Y & BIT7) == BIT7) LOGIC=1;
函式中:
for (i = 0; i < 256; i++) ...
DIGITAL OUTPUT 由 0 往 255 遞增. 我們要找的是 PCA_9555_SLAVE_2 P0_7 (COMPARATOR LOGIC) 由 0 轉變到 1 的時機; 當 COMPARATOR LOGIC 由 0 轉變到 1, 函式返回, 由參數 value 傳回上一次的 DIGITAL OUTPUT.
馬達正轉 / 反轉 / 停止
馬達的 IO 是 PCA_9555_SLAVE_1 之 P0_2 以及 P0_3 , 因此:
正轉:
// motor+ v &= ~BIT2; v |= BIT3;
反轉:
// motor- v &= ~BIT3; v |= BIT2;
停止:
// motor stop v |= BIT2; v |= BIT3;
IO Input / Output Settings
1. 馬達 IO 設為 OUTPUT:
// motor IO
res = i2c_read_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
v &= ~BIT2; v &= ~BIT3;
res = i2c_write_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
2. DIGITAL OUTPUT:
// X output
v = 0X00; res = i2c_write_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_1, &v, 1);
if (-1 == res)
printf("(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
3. LOGIC INPUT:
// Y input
res = i2c_read_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
v |= BIT7; res = i2c_write_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
A/D Converter 伺服馬達制動 - mbrake.c
把上述綜合起來, 筆者寫了這個伺服馬達制動的程式 mbrake.c ; 讀者請別小看它, 我已經看過兩個軟體工程師掛點 (GG了) 在這 A/D Converter 伺服馬達制動程式; 在當時好像只有我做出來.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#include <netinet/tcp.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <asm/ioctl.h>
#include <asm/errno.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
//////////////////////////////////////////////////////////////////////
// define
//////////////////////////////////////////////////////////////////////
#define BIT0 (1<<0)
#define BIT1 (1<<1)
#define BIT2 (1<<2)
#define BIT3 (1<<3)
#define BIT4 (1<<4)
#define BIT5 (1<<5)
#define BIT6 (1<<6)
#define BIT7 (1<<7)
#define PCA_9555_SLAVE_0 0X24
#define PCA_9555_SLAVE_1 0X20
#define PCA_9555_SLAVE_2 0X21
#define PCA_9555_INPUT_0 0
#define PCA_9555_INPUT_1 1
#define PCA_9555_OUTPUT_0 2
#define PCA_9555_OUTPUT_1 3
#define PCA_9555_POLARITY_0 4
#define PCA_9555_POLARITY_1 5
#define PCA_9555_CONFIG_0 6
#define PCA_9555_CONFIG_1 7
#ifdef USE_SOC_ADC
#define GPIO_IOCTL_ID 'G'
#define GPIO_IOCTL_SET_VALUE _IOW(GPIO_IOCTL_ID, 0, int)
#define GPIO_IOCTL_GET_VALUE _IOR(GPIO_IOCTL_ID, 1, int)
#define GPIO_IOCTL_SET_INPUT _IOW(GPIO_IOCTL_ID, 2, int)
#define GPIO_IOCTL_SET_PROPERTY _IOW(GPIO_IOCTL_ID, 3, int)
#define GPIO_IOCTL_SET_INT _IOW(GPIO_IOCTL_ID, 4, int)
#define GPIO_FUNC_ORG 0
#define GPIO_FUNC_GPIO 1
#define GPIO_DIR_OUTPUT 0
#define GPIO_DIR_INPUT 1
#define GPIO_PULL_LOW 0
#define GPIO_PULL_HIGH 1
#define GPIO_PULL_FLOATING 2
#define GPIO_NO_PULL 3
#define GPIO_IRQ_DISABLE 0
#define GPIO_IRQ_ENABLE 1
#define GP_ADC_MAGIC 'G'
#define IOCTL_GP_ADC_START _IOW(GP_ADC_MAGIC,0x01,unsigned long)
#define IOCTL_GP_ADC_STOP _IO(GP_ADC_MAGIC,0x02)
#define MK_GPIO_INDEX(ch, func, gid, pin) \
(((ch) << 24) | ((func) << 16) | ((gid) << 8) | (pin))
typedef struct gpio_content_s {
unsigned int pin_index;
unsigned int value;
unsigned int debounce;
} gpio_content_t;
#endif
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
static int i2c_write_data(int fd_i2c, int bus_adr, int sub_adr, const unsigned char *data, int length) {
int res;
char buf[256];
int i;
if (0 == data || 0 >= fd_i2c) return -1;
res = ioctl(fd_i2c, I2C_SLAVE_FORCE, bus_adr);
if(res < 0) return -1;
buf[0] = sub_adr;
for(i = 1; i <= length; i++) buf[i] = data[i -1];
if(write(fd_i2c, buf, length + 1) != (length + 1)) return -1;
return length;
}
static int i2c_read_data(int fd_i2c, int bus_adr, int sub_adr, unsigned char *data, int length) {
int res;
if (0 == data || 0 >= fd_i2c) return -1;
res = ioctl(fd_i2c, I2C_SLAVE_FORCE, bus_adr);
if(res < 0) return -1;
if(write(fd_i2c, &sub_adr, 1) != 1) return -1;
return read(fd_i2c, data, length);
}
#ifndef USE_SOC_ADC
static int ReadI2cAdc(int fd, unsigned char *value) {
int i;
int probe = 0;
unsigned char X = 0;
unsigned char Y = 0;
static unsigned char O = 0; // last X
if (-1 == fd) return 0;
// DIGITAL OUTPUT 由 0 往 255 遞增
for (i = 0; i < 256; i++) {
if (-1 == i2c_write_data(fd, PCA_9555_SLAVE_2, PCA_9555_OUTPUT_1, &X, 1)) {
printf("(%s %d) i2c_write_data FAIL \n", __FILE__, __LINE__);
return 0;
}
usleep(1000);
if (-1 == i2c_read_data(fd, PCA_9555_SLAVE_2, PCA_9555_INPUT_0, &Y, 1)) {
printf("(%s %d) i2c_read_data FAIL \n", __FILE__, __LINE__);
return 0;
}
usleep(1000);
// COMPARATOR
if ((Y & BIT7) == BIT7) {
// COMPARATOR LOGIC=1
probe = 1;
break;
}
// COMPARATOR LOGIC=0
O = X;
if (X >= 255) X = 0;
else X++;
}
if (0 == probe) return 0;
*value = O;
return 1;
}
#endif
void PrintUsage(void) {
char file[128];
int i = 0;
memset(file, 0, sizeof(file));
strcpy(file, __FILE__);
for (; i < 128; i++) if ('.' == file[i]) file[i] = 0;
printf("Usage: %s -L [BRAKE-LEVEL] -T [TIMEOUT]\n", file);
printf("Example: %s -L 6 -T 6\n", file), exit(1);
}
int main(int argc, char *argv[]) {
int adc1 = -1;
int t;
time_t timeout = 6;
int i2c0 = -1;
int level = 6;
static unsigned int adc = 0;
time_t now = 0;
int D = 0;
int res;
int o;
int len = 0;
static unsigned char v;
int i;
int ok = 0;
setbuf(stdout, 0);
if (argc < 2) PrintUsage();
while ((o = getopt(argc, argv, "L:l:T:t:")) != -1) {
switch (o) {
default:
break;
case '?':
PrintUsage();
break;
case 't':
case 'T':
len = strlen(optarg);
for (i = 0; i < len; i++) {
if ('0' != optarg[i] && '1' != optarg[i] && '2' != optarg[i] && '3' != optarg[i] && '4' != optarg[i] && '5' != optarg[i] && '6' != optarg[i] && '7' != optarg[i] && '8' != optarg[i] && '9' != optarg[i]) {
PrintUsage();
}
}
t = atoi(optarg);
if (t <= 0) PrintUsage();
timeout = (time_t)t;
timeout &= 0XFF;
break;
case 'l':
case 'L':
len = strlen(optarg);
for (i = 0; i < len; i++) {
if ('0' != optarg[i] && '1' != optarg[i] && '2' != optarg[i] && '3' != optarg[i] && '4' != optarg[i] && '5' != optarg[i] && '6' != optarg[i] && '7' != optarg[i] && '8' != optarg[i] && '9' != optarg[i]) {
PrintUsage();
}
}
level = atoi(optarg);
if (level <= 0) PrintUsage();
level &= 0XFF;
break;
}
}
i2c0 = open("/dev/i2c-0", O_RDWR);
if (-1 == i2c0)
printf("(%s %d) OPEN(/dev/i2c-0) FAIL, EXIT \n", __FILE__, __LINE__), exit(1);
// motor IO output
res = i2c_read_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
v &= ~BIT2; v &= ~BIT3;
res = i2c_write_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
#ifdef USE_SOC_ADC
adc1 = open("/dev/adc", O_RDWR);
if (-1 == adc1)
printf("(%s %d) open('/dev/adc') Fail \n", __FILE__, __LINE__), exit(1);
#else
// DIGITAL OUTPUT
v = 0X00; res = i2c_write_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_1, &v, 1);
if (-1 == res)
printf("(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
// LOGIC INPUT
res = i2c_read_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
v |= BIT7; res = i2c_write_data(i2c0, PCA_9555_SLAVE_2, PCA_9555_CONFIG_0, &v, 1);
if (-1 == res)
printf("(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__), close(i2c0), exit(1);
#endif
time(&now);
timeout += now;
for(;;) {
int gpio;
#if 1
if (-1 == i2c_read_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_INPUT_0, &v, 1)) {
printf("\n(%s %d) i2c_read_data() FAIL, EXIT \n", __FILE__, __LINE__);
goto BAITOUT;
}
// motor stop
v |= BIT2; v |= BIT3;
if (-1 == i2c_write_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_OUTPUT_0, &v, 1)) {
printf("\n(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__);
goto BAITOUT;
}
#endif
time(&now);
if (now > timeout) {
printf("\n(%s %d) TIMEOUT, EXIT \n", __FILE__, __LINE__);
goto BAITOUT;
}
#ifdef USE_SOC_ADC
gpio = open("/dev/gpio", O_RDWR);
if(-1 == gpio) {
printf("GPIO OPEN ERROR\n"); break;
} else {
gpio_content_t ctx;
ctx.pin_index = MK_GPIO_INDEX(0, 0, 9, 20);
ctx.value = 0;
ctx.value = GPIO_PULL_FLOATING; ctx.debounce = 0;
ioctl(gpio, GPIO_IOCTL_SET_INPUT, &ctx);
close(gpio);
if (-1 == ioctl(adc1, IOCTL_GP_ADC_START, 1)) { printf("ADC_START ERROR\n"); break; }
if (-1 == read(adc1, &adc, sizeof(int))) { printf("ADC_READ ERROR\n"); break; }
if (-1 == ioctl(adc1, IOCTL_GP_ADC_STOP, 1)) { printf("ADC_STOP ERROR\n"); break; }
// 10-bit ; hence we need shift right 2 bits
adc >>= 2;
}
#else
if(0 != ReadI2cAdc(i2c0, &adc)) printf("L=%d A=%d\n", level, adc);
else { printf("ADC ERROR\n"); break; }
#endif
D = (int)level - (int)adc;
if (D <= 1 && D >= -1) {
ok = 1;
break;
} else {
if (D >= 0) {
// motor+
v &= ~BIT2; v |= BIT3;
} else {
// motor-
v &= ~BIT3; v |= BIT2;
}
if (-1 == i2c_write_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_OUTPUT_0, &v, 1)) {
printf("\n(%s %d) i2c_write_data() FAIL, EXIT \n", __FILE__, __LINE__);
goto BAITOUT;
}
usleep(20 * 1000);
}
}
if (ok) printf("\nMORTOR BRAKE, LEVEL=%d ADC=%d\n", level, adc);
BAITOUT:
// motor stop
v |= BIT2; v |= BIT3;
i2c_write_data(i2c0, PCA_9555_SLAVE_1, PCA_9555_OUTPUT_0, &v, 1);
close(i2c0);
close(adc1);
return 0;
}
使用方式 (轉動馬達至 Level 99 的位置)
# mbrake -L 99 -T 6
-L 面是 level ; -T 後面是 time out
Treadmill 的 incline 或是 bike 的 workload 原理可能是這樣的.
Email: jasonc@mail2000.com.tw . 請尊重原創, 使用文件時載明出處. 謝謝.
