Cần giúp Chống nhiễu cho ADC

huunho

Thạc sỹ
#1
Chào các bạn.
Mình đang đọc ADC 1 kênh của stm32f103c8t6, quá trình đọc thì bình thường nhưng giá trị trả về nhảy linh tinh ở 2 số cuối. cụ thể ADC 12 bít thì giá trị lớn nhất trả về là 4096 thì giá trị này cứ nhảy linh tinh ở 2 số cuối là 96. nếu gắn AIN0 vào VCC thì đọc dc là 4030, còn gắn vào GND thì đọc dc là 0000 thì ko nhảy, nhưng các giá trị trung gian thì nhảy rất nhanh do nhiễu.
Ai đã làm qua rồi xin cho mình lời khuyên để khử nhiễu. cám ơn mọi người đã đọc
 

Đính kèm

BuiBachTuanAnh

Quản trị viên
Thành viên BQT
#2
Hiện tượng xử lý nhiễu thì muôn thủa, mỗi project có cách xử lý khác nhau và tuỳ vào từng điều kiện hoạt động của mạch. Nhiễu ADC cũng vậy, tuy nhiên trước hết phải tuân thủ nguyên tắc theo khuyến cáo của nhà sản xuất đã.
F49901CC-CAA0-4468-A275-1044847895D5.png

F7D9C67D-9C6E-433F-A338-54382BE849E7.png
Với chú ý một số điểm sau:
- Đầu vào Analog cần được bọc chống nhiễu.
- Vref cần được cấp chuẩn. Như bọn tư bản nó hay dùng một con IC chuyên dụng để cấp nguồn riêng cho thằng này.
Mình lâu quá rồi ko làm và cũng không làm nhiều nên góp ý với bạn chút cho rôm rả thôi :)
 

huunho

Thạc sỹ
#3
cám ơn bạn quan tâm.
Mình tét theo mạch trên thì thấy nhiễu giảm đi 30% , nên vẫn chưa ổn. Mình đã tính trung bình 100 giá trị ADC liên tiếp để lấy ra giá trị gần đúng nên cũng khá ổn rồi. Bạn có biết cách xây dựng bộ lọc kalman thì nói vắn tắt giúp mình?
 

Concept

Kỹ sư
#4
cám ơn bạn quan tâm.
Mình tét theo mạch trên thì thấy nhiễu giảm đi 30% , nên vẫn chưa ổn. Mình đã tính trung bình 100 giá trị ADC liên tiếp để lấy ra giá trị gần đúng nên cũng khá ổn rồi. Bạn có biết cách xây dựng bộ lọc kalman thì nói vắn tắt giúp mình?
Tìm được đoạn clip này nói về giảm nhiễu ADC. Tuy có hơi khác bạn một chút là tác giả lấy 128 giá trị trung bình liên tiếp, giải thuật xem ra có vẻ khá đơn giản nhưng rất thông minh, Bạn xem thử có giúp gì được không ?

Chú ý: Trong phần dưới của clip có đường dẫn để bạn tải về code mẫu...

Nguồn

 

huunho

Thạc sỹ
#5
dường như code đính kèm ko giống trong video nhưng mình cũng hiểu ý của chủ video rồi. mình tính trung bình theo kiểu đệ quy để ko tốn ram
 

huunho

Thạc sỹ
#6
cụ thể thế này:
void ADC1_2_IRQHandler(void)
{
/* USER CODE BEGIN ADC1_2_IRQn 0 */
//HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
static uint32_t vol[20],k,a;
//uint16_t i;
//k++;
//if(k>99) k=0;
//vol[k] = HAL_ADC_GetValue(&hadc1);
k = a;
a=HAL_ADC_GetValue(&hadc1);
a = (59*k +a)/60;
num2 = a*15840/4096;
num1 = num2 / 10000;
/* USER CODE END ADC1_2_IRQn 0 */
HAL_ADC_IRQHandler(&hadc1);
/* USER CODE BEGIN ADC1_2_IRQn 1 */

/* USER CODE END ADC1_2_IRQn 1 */
}
 

Concept

Kỹ sư
#7
cụ thể thế này:
void ADC1_2_IRQHandler(void)
{
/* USER CODE BEGIN ADC1_2_IRQn 0 */
//HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
static uint32_t vol[20],k,a;
//uint16_t i;
//k++;
//if(k>99) k=0;
//vol[k] = HAL_ADC_GetValue(&hadc1);
k = a;
a=HAL_ADC_GetValue(&hadc1);
a = (59*k +a)/60;
num2 = a*15840/4096;
num1 = num2 / 10000;
/* USER CODE END ADC1_2_IRQn 0 */
HAL_ADC_IRQHandler(&hadc1);
/* USER CODE BEGIN ADC1_2_IRQn 1 */

/* USER CODE END ADC1_2_IRQn 1 */
}
Nhìn code của bạn mình cũng không hiểu nhiều lắm, chắc trình độ mình còn non :-)

Không rõ bạn nhận xét thế nào về giải thuật của đoạn video clip, theo mình từ phút 3:41 của đoạn clip tác giả đã thể hiện một đoạn code mà trong đó, đoạn chương trình không cập nhật lên LCD tức thời giá trị đọc được nếu giá trị này khác so với giá trị trước đó khoảng 1 hay 2 đơn vị ... Thay vào đó chương trình đọc tiếp thêm hai lần nữa, nếu giá trị này vẫn giữ giá trị thay đổi so với 2 chu kỳ đọc trước đó, nó sẽ cập nhật lên LCD còn không bỏ qua coi như nhiễu.

Tất nhiên như tác giả cũng thừa nhận, còn có nhiều giải pháp khác... có thể hay hơn nhưng cũng phải nhìn nhận giải pháp của tác giả khá đơn giản nhưng rất thông minh.

Chúc vui.
 

huunho

Thạc sỹ
#8
Cám ơn bạn concept đã góp ý. Bạn quá khiên tốn rồi. trình độ của bạn thì rất nhiều người không bằng. Do ở code trên mình ko giải thích gì nên bạn chưa thấy rõ ràng thôi.
Cụ thể là đoạn code trên là hàm ngắt của ADC1, mỗi khi chuyển đổi ADC xong thì chíp nhảy vào ngắt và tính toán như sau, giá trị adc cũ được giữ lại bởi biến k:
k = a;
còn giá trị adc mới được lưu và biến a
a=HAL_ADC_GetValue(&hadc1);
sau đó giá trị mới chỉ được tính bằng 1/60 kết quả sẽ hiển thị ra màn hình , trong khi kết quả cũ bằng 59/ 60 kết quả , điều này có tác dụng chống nhiễu bằng phần mềm đáng kể và giảm dung lượng ram và các phép toán cồng kềnh:
a = (59*k +a)/60;
kết quả hiển thị trên màn hình khá ổn định:
num2 = a*15840/4096; (điện áp hiển thị trên màn hình led 7 đoạn)

Hiện tại mình gặp vấn đề sau: mình nối trở R1 = 1k vào chân A0 (ADC IN0) với GND, còn trở R2 = 47k nối vào A0 (ADC IN0) với điện áp cần đo. với điện áp mẫu VREF = 3.3V thì điện áp đo được của mạch ADC là
(VREF/R1) *(R2+R1) = (3.3/1)*(47+1)=158.4V
do giá trị tính toán là số nguyên nên ta nhân nó với 100 để có thể tính với số nguyên
158.4 * 100 = 15840
với ADC 12 bít của stm32f103c8t6, giá trị tối đa đọc dc của ADC là 2^12 = 4096 nhưng do tính từ số 0 ở đầu nên chỉ còn 4095
Giả sử giá trị ADC đọc được là a thì điện áp đo được là:
(a/4095)*(VREF/R1)*(R1+R2) = (a/4095)*158.4 V
Nhân kết quả trên với 100 để làm việc với số nguyên, trên màn hình sẽ hiển thị số nguyên:
num1 = 15840*a/4095
ví dụ ADC đo được a = 100 thì trên màn hình hiển thị
num1 = 15840*100/4095 = 386
thì ta cố tình hiểu nó là 3.86V
tất cả các tính toán trên là lý thuyết, còn thực tế lại khác. mình tăng dần điện áp từ 0 đến 5V thì màn hình mới bắt đầu nhích số từ 0000 chuyển sang 0031, tăng lên đến 10V thì hiển thị dc 167( tức 1.67V) 15V hiển thi 282. tức 2.82V
sau đó giảm dần về đến 3v thì màn hình hiển thị 0000.
bạn concept có thể tìm lỗi giúp mình không?
 

Concept

Kỹ sư
#9
Cám ơn bạn concept đã góp ý. Bạn quá khiên tốn rồi. trình độ của bạn thì rất nhiều người không bằng. Do ở code trên mình ko giải thích gì nên bạn chưa thấy rõ ràng thôi.
Cụ thể là đoạn code trên là hàm ngắt của ADC1, mỗi khi chuyển đổi ADC xong thì chíp nhảy vào ngắt và tính toán như sau, giá trị adc cũ được giữ lại bởi biến k:
k = a;
còn giá trị adc mới được lưu và biến a
a=HAL_ADC_GetValue(&hadc1);
sau đó giá trị mới chỉ được tính bằng 1/60 kết quả sẽ hiển thị ra màn hình , trong khi kết quả cũ bằng 59/ 60 kết quả , điều này có tác dụng chống nhiễu bằng phần mềm đáng kể và giảm dung lượng ram và các phép toán cồng kềnh:
a = (59*k +a)/60;
kết quả hiển thị trên màn hình khá ổn định:
num2 = a*15840/4096; (điện áp hiển thị trên màn hình led 7 đoạn)

Hiện tại mình gặp vấn đề sau: mình nối trở R1 = 1k vào chân A0 (ADC IN0) với GND, còn trở R2 = 47k nối vào A0 (ADC IN0) với điện áp cần đo. với điện áp mẫu VREF = 3.3V thì điện áp đo được của mạch ADC là
(VREF/R1) *(R2+R1) = (3.3/1)*(47+1)=158.4V
do giá trị tính toán là số nguyên nên ta nhân nó với 100 để có thể tính với số nguyên
158.4 * 100 = 15840
với ADC 12 bít của stm32f103c8t6, giá trị tối đa đọc dc của ADC là 2^12 = 4096 nhưng do tính từ số 0 ở đầu nên chỉ còn 4095
Giả sử giá trị ADC đọc được là a thì điện áp đo được là:
(a/4095)*(VREF/R1)*(R1+R2) = (a/4095)*158.4 V
Nhân kết quả trên với 100 để làm việc với số nguyên, trên màn hình sẽ hiển thị số nguyên:
num1 = 15840*a/4095
ví dụ ADC đo được a = 100 thì trên màn hình hiển thị
num1 = 15840*100/4095 = 386
thì ta cố tình hiểu nó là 3.86V
tất cả các tính toán trên là lý thuyết, còn thực tế lại khác. mình tăng dần điện áp từ 0 đến 5V thì màn hình mới bắt đầu nhích số từ 0000 chuyển sang 0031, tăng lên đến 10V thì hiển thị dc 167( tức 1.67V) 15V hiển thi 282. tức 2.82V
sau đó giảm dần về đến 3v thì màn hình hiển thị 0000.
bạn concept có thể tìm lỗi giúp mình không?
Có cái gì đó không ổn ở đây :-), đặc biệt ở phần bạn tính từ 3.3 V lên... 158.4V... nó giống như bạn đang suy nghĩ và tính toán về một mạch.... tăng áp DC ... đây là phần mình không hiểu. Phần Vref cho mạch ADC nói chung dường như khác với những gì bạn dùng để tính toán lý thuyết.

Nếu được bạn cho mình xem bản vẽ phần cứng của mạch phân áp thì mình sẽ dễ hình dung hơn.

Mình ít sử dụng STM32 mà chủ yếu chỉ chơi Arduino thôi... nên đành phải tìm trong google vậy... và đây là một trang web mình tìm thấy nguồn https://letanphuc.net/2016/07/stm32f0-adc/. Tác giả trang web có vẻ là người Việt mà sao thấy toàn viết bằng tiếng Anh kể cả các video clip minh họa tác giả cũng giải thích bằng tiếng Anh luôn.

Nhưng thôi bỏ qua chuyện này cái được là trang web này viết rất chi tiết về ADC của STM32 bạn thử xem có giải quyết được gì không.

Rất mong được trao đổi cùng bạn. Chúc vui.
 

Thai Phan

Sinh viên đại học
#10
Hiện tại mình gặp vấn đề sau: mình nối trở R1 = 1k vào chân A0 (ADC IN0) với GND, còn trở R2 = 47k nối vào A0 (ADC IN0) với điện áp cần đo. với điện áp mẫu VREF = 3.3V thì điện áp đo được của mạch ADC là
(VREF/R1) *(R2+R1) = (3.3/1)*(47+1)=158.4V
do giá trị tính toán là số nguyên nên ta nhân nó với 100 để có thể tính với số nguyên
158.4 * 100 = 15840
với ADC 12 bít của stm32f103c8t6, giá trị tối đa đọc dc của ADC là 2^12 = 4096 nhưng do tính từ số 0 ở đầu nên chỉ còn 4095
Giả sử giá trị ADC đọc được là a thì điện áp đo được là:
(a/4095)*(VREF/R1)*(R1+R2) = (a/4095)*158.4 V
Nhân kết quả trên với 100 để làm việc với số nguyên, trên màn hình sẽ hiển thị số nguyên:
num1 = 15840*a/4095
ví dụ ADC đo được a = 100 thì trên màn hình hiển thị
num1 = 15840*100/4095 = 386
thì ta cố tình hiểu nó là 3.86V
tất cả các tính toán trên là lý thuyết, còn thực tế lại khác. mình tăng dần điện áp từ 0 đến 5V thì màn hình mới bắt đầu nhích số từ 0000 chuyển sang 0031, tăng lên đến 10V thì hiển thị dc 167( tức 1.67V) 15V hiển thi 282. tức 2.82V
sau đó giảm dần về đến 3v thì màn hình hiển thị 0000.
bạn concept có thể tìm lỗi giúp mình không?
Đầu tiên, có thể không liên quan tới lỗi bạn đang bị, nhưng giá trị ADC đọc được chia cho 4096 chứ không phải 4095 nhé. Đây là cái nhiều người tới giờ vẫn nhầm. Mặc dù đúng là có 12 bit thì chỉ từ 0 tới 4095 chứ không thể 4096 nhưng giá trị voltage ADC đo được lại không phải là một số chính xác mà là một dải giá trị. Ngay trong cái hình bạn đính kèm cũng ghi rõ rồi.

Mình nghi là do code không thực thi đúng ý đồ bạn muốn. Ví dụ có thể vì nhân chia số lớn nên nó bị tràn trong một trường hợp cụ thể nào đó chẳng hạn?

Nhưng tạm thời bây giờ mình đề nghị bạn thử làm thế này xem sao:
- Về tính toán ADC, vì num1 = a * 15840 / 4096 hay num1 = a * 495 / 128 nên khi tính toán trong code bạn nên viết num1 = (a * 495)>>7. Với vi điều khiển thì việc chia 128 sẽ chậm hơn dịch 7 bit về bên phải rất nhiều. Đó cũng là lý do anh bạn trong video trên chọn giá trị tính tổng chia trung bình là 128 chứ không phải 100 như bạn.
- Để xác định lỗi có phải do khâu tính toán bị tràn số trong vài trường hợp cụ thể nào đó không, thì bạn thử output giá trị a lên máy tính hoặc LCD, tự tính bằng tay và so sánh với giá trị điện áp thực tế xem sao?

Quay lại chủ đề chống nhiễu ADC, kinh nghiệm cá nhân của mình là:
- Vref phải chuẩn. Nếu chọn Vref là VDD luôn thì nguồn nuôi phải rất chuẩn, không bị dao động, không bị chia nguồn nằm gần phần công suất nào lớn và phải có tụ ổn áp giá trị lớn ở gần IC, tụ lọc cao tần ở các chân VDD hay Vref.
- Tùy theo đầu vào cần đo đạc là tín hiệu có tính chất gì mà thiết kế phần chống nhiễu cho phù hợp. Ví dụ biết rõ tín hiệu đó ít dao động tần số cao (biến trở chỉnh vặn, đo điện áp xoay chiều 50Hz...) thì chọn tụ lọc nhiễu tần số cao để lọc các nhiễu cao tần đi. Tần số cut off là bao nhiêu thì tùy bạn tính toán. Thông thường ứng dụng không phức tạp mình chỉ dùng tụ 104 là đủ.
- Chống nhiều bằng phần mềm ví dụ như cách lấy giá trị trung bình hoặc chỉ thay đổi hiển thị khi giá trị thu được vượt quá một giá trị nào đó so với giá trị cũ.
- Lưu ý là tần số lấy mẫu ADC nữa, không được vượt quá tần số ADC max quy định trong datasheet tương ứng với độ phân giải ADC hiện tại.

Truớc giờ mình chỉ có kinh nghiệm làm việc với PIC, dsPIC chứ chưa dùng STM bao giờ nên có thể một vài thông tin về Vref, VDD hay ADC module của STM mình đã nói sai. Mong mọi người góp ý.
 

Quảng cáo Google